0%

Skia官方网站

Window 10

Python 2.7.8
Window 10 1909 (18363.1379)
Window 10 SDK
VS 2019
LLVM 11.0.0

  • 准备Python2,并且设置Python2的优先级高于Python3(在环境变量里Python2的路径比Python3先出现即可)
    否则会出现depot_tools错误和编译时ICU生成错误
  • 拉取部署工具库
    git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
  • 拉取Skia仓库
    git clone https://skia.googlesource.com/skia.git
  • 同步需要的第三方库
    cd skia
    python2 tools/git-sync-deps
  • 设置VS路径
    在 bn\BUILDCONFIG.gn 文件中,设置
    win_vc = <VS_Path>
    win_vc = "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC"
  • 生成编译文件
    Skia 编译成静态连接库中,会自动设置 Runtime 为 MT,需要手动设置为 MD
    Win 32位编译需要在输出文件夹中将 toolchain.ninjarule command 中所有的
    cmd.exe /c C:/Program Files (x86)/Windows Kits/10/bin/SetEnv.cmd /x86 &&
    去掉,实际上既没有这个脚本,也不需要这个脚本
    • win32 Debug 静态连接 /MDd Angle
      bin\gn gen out/Debug --args="is_debug=true target_cpu=\"x86\" extra_cflags=[\"/MDd\"] clang_win=\"C:/Program Files (x86)/LLVM\" skia_use_angle = true"
    • win32 Release 静态连接 /MD Angle
      bin\gn gen out/Release --args="is_debug=false target_cpu=\"x86\" extra_cflags=[\"/MD\"] clang_win=\"C:/Program Files (x86)/LLVM\" skia_use_angle = true"
  • 编译
    ninja -C <outpath>

成功案例

  • Qt 6.0.1
  • Window 10 18363.1316 使用Unicode UTF-8提供全球语言支持
  • C/C++ Optimizing Compiler Version 19.28.29336 for x86
  • CMake version 3.19.4 CMake 需要大于3.18.3,否则输出Ninja工程文件时无法同时配置debug和release
  • Python 3.9.1
  • ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x64-mingw32]
  • perl 5, version 32, subversion 1 (v5.32.1) built for MSWin32-x64-multi-thread
1
c:\Code\qsrc>configure -opensource -confirm-license -prefix C:\Qt\6.0.1\msvc2019 -debug-and-release

现在的确是没时间研究。先把项目的进度推进了。记录研究过程回过头有时间再看看

研究的起因在做图形引擎里的文字绘制功能,准备把字符纹理填充到一张、多张纹理,以减少drawcall。搜索了一番,发现事情并不简单,这个关于空间利用效率的算法有很多应用场所————计算机纹理打包,板材切割,集装箱打包货物等等。

简单的搜索了下,用中文基本没搜出啥有用的信息,用的关键字————矩形打包算法、排料算法
知网找了写有关的,比较新的论文看了下

中文基本确定只有知网里没看过的文章值得再看看,搜索引擎的不用浪费时间

英文搜索了下,关键字基本可以锁定在packingnesting。先找了写有关的页面看了下

英文的没怎么细看,随便搜搜看了下,有用的信息不少,可以花时间搜索细看

一步到胃

知名的C算法库stb/stb_rect_pack.h,无私奉献的公共领域许可。
看了下介绍,效果不太好。不过现在只需要一个大概能用的就行。

1
SetFlag\((?<var>.*),(?<value>.*)\)

选择了SetFlag(A,B)中的并以var{var}和{value}储存,值得注意的是,无嵌套能力,如果AB的表达式中有逗号,则不能正确的分组

FreeType的bitmap中的数据顺序和屏幕坐标顺序一致,与OpenGL中texture的数据Y轴反转。第一个数据代表左上角,从左到右处理,最后一个数据代表右下角C++

参考OpenGL4 glTexImage2D

The first element corresponds to the lower left corner of the texture image. Subsequent elements progress left-to-right through the remaining texels in the lowest row of the texture image, and then in successively higher rows of the texture image. The final element corresponds to the upper right corner of the texture image.

buffer中第一个元素代表左下角,从左到右处理,最后一个元素代表右上角

glsl中设置了贴图采样,代码中设置贴图一般需要以下步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(...);

// 设置环绕和过滤设置
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// 自动生成mipmap
//glGenerateMipmap(GL_TEXTURE_2D);

mipmap环绕过滤设置至少要有一个,或者glTexImage2D()中设置了mipmap。按照经验,OpenGLOpenGL ES 中均需要这样设置

gles程序遇到了一个在Windows 10 UHD630 下能正确运行却在 macOS 10.15.7 Iris 645却效果不对的问题。macOS下 OpenGL不能抓帧分析,经过了一番努力才发现了问题所在。

问题在于vertex shader中有好几个attribute, 在glVertexAttribPointer()按照了声明顺序默认了他们的索引。实际上 macOS 下attribute的默认顺序可能是倒过来了。使用了glGetAttribLocation()获取实际索引后,显示效果就正常了。

在GL3+的时候习惯了在vs中加上layout强制布局。在GLES中没有layout这回事,习惯性地沿用了以前做法,没想到真的出了问题

Question / 问题

按照需求,需要在一些底层控件上叠加一些UI。比如底层是CAD的绘制页面或者视频播放页面,上面要放一些Label或者Button。上层需要添加的小空间往往不好直接加在最底层的控件上(底层已经有特殊的布局了)

Search / 研究过程

中英文都搜索了不少页面,大多是下面几种方法

  • 建立两个控件,最上层设置透明背景,属性设置ToolTip一类来实现
    • 属性设置的过程很复杂,先后顺序有特定要求
    • 跨平台有问题
  • 利用QGridLayout布局,两个控件按先后顺序添加在同一个格子里
  • 使用QStackLayout
  • 使用类似与Popup Menu的方法
    • Popup属性的窗口失去焦点后就会自动隐藏

除了特别列出来的问题,这些方法都有一个根本的问题————所有的消息都会被最上层的Widget拦截。例如鼠标指针跨过透明背景点击了底层的控件,消息仍会被最上层的控件拦截。

  • 有些热心的开发者指出可以在最顶层的控件安装事件过滤器和转发器来实现消息可以合理的转发到底层。
    • 过滤和转发所有的事件会带来巨大的工作量,代码复用性几乎没有
  • 也可以通过直接添加上层控件到父窗口来实现————使用setParent()而不是layout()->addWidget()
    • 没有布局功能,需要在父类实现resizeEvent()moveEvent()的转发来手动计算布局
    • 工作量也比较大,代码复用性也不好

Solution / 解决方案

最后Qt的官方文档Layout Management给了我启发。示例实现了个Qt没有自带而Java中有的布局CardLayout

The CardLayout class is inspired by the Java layout manager of the same name.

其中设置子控件geometry部分的代码可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void CardLayout::setGeometry(const QRect &r)
{
QLayout::setGeometry(r);

if (m_items.size() == 0)
return;

int w = r.width() - (m_items.count() - 1) * spacing();
int h = r.height() - (m_items.count() - 1) * spacing();
int i = 0;
while (i < m_items.size()) {
QLayoutItem *o = m_items.at(i);
QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
o->setGeometry(geom);
++i;
}
}

其中最关键的一句是o->setGeometry(geom);。这表明Layout的本质是自动设置子控件的大小和位置。而我直接利用Qt原有的布局功能来计算每一层控件的大小和位置,再根据计算的结果来手动设置每个控件的大小的位置。不就能优雅地达到目的了吗。

实现起来比想象中的要更简单。先贴出代码
OverlapLayout.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#pragma once

#include <QLayout>
#include <QList>

class OverlapLayout : public QLayout
{
public:
OverlapLayout(QWidget* parent = nullptr);
~OverlapLayout() = default;

/// <summary>
/// 新增的Item会转发到最后一层layout的addItem()
/// </summary>
/// <param name="item"></param>
void addItem(QLayoutItem* item) override;
QSize sizeHint() const override;
QSize minimumSize() const override;
int count() const override;
QLayoutItem* itemAt(int) const override;
QLayoutItem* takeAt(int) override;
void setGeometry(const QRect& rect) override;

void AddLayout(QLayout*);

private:
QList<QLayout*> things;
};

OverlapLayout.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include "OverlapLayout.h"

#include <QWidget>

OverlapLayout::OverlapLayout(QWidget* parent)
:QLayout(parent)
{

}

void OverlapLayout::addItem(QLayoutItem* item)
{
if(!things.empty())
{
things.last()->addItem(item);
}
}

QSize OverlapLayout::sizeHint() const
{
QSize s(0, 0);
for (auto e : things)
{
s = s.expandedTo(e->sizeHint());
}

return s;
}

QSize OverlapLayout::minimumSize() const
{
QSize s(0, 0);
for (auto e : things)
{
s = s.expandedTo(e->minimumSize());
}

return s;
}

int OverlapLayout::count() const
{
int num = 0;
for (auto e : things)
{
num += e->count();
}

return num;
}

QLayoutItem* OverlapLayout::itemAt(int index) const
{
int num = 0;
int next = 0;
for (auto e : things)
{
next = num + e->count();
if (index < next)
{
return e->itemAt(index - num);
}
num = next;
}

return nullptr;
}

QLayoutItem* OverlapLayout::takeAt(int index)
{
invalidate();

int num = 0;
int next = 0;
for (auto e : things)
{
next = num + e->count();
if (index < next)
{
e->invalidate();
return e->takeAt(index - num);
}
num = next;
}

return nullptr;
}

void OverlapLayout::setGeometry(const QRect& r)
{
if (things.size() == 0)
return;

for (auto e : things)
{
e->invalidate();
e->setGeometry(r);
}
}

void OverlapLayout::AddLayout(QLayout* layout)
{
if(layout)
{
invalidate();
things.append(layout);
addChildLayout(layout);
layout->invalidate();
}
}

实现的思路也非常简单。储存多层QLayout*, 在setGeometry()中直接把整体的QRect传入到每一个层中计算就好。这样就可以优雅的使用添加多层布局和使用Qt中原有的功能。

Usage / 使用方法

使用的方法也非常简单。父窗口的layout设置为OverlapLayout对象,把每一层的布局设置好,按顺序使用OverlapLayout::AddLayout()添加到多层布局里就好

Doubts / 疑点

目前存在的一个疑问是————如何保证每一层的顺序呢。从官方例子中的CardLayout::setGeometry中看到的表现是,最后设置geometry的QLayoutItem在最上层,在OverlapLayout实际体验中也是如此表现,但是目前还不确定一定会如此

使用中发现和层序有关的问题,在QScrollBar第一次触发hover事件的时候,QScrollBar会提到最上面刷新一下,又回到原来的层次,但是有些控件会被挡住。触发下被挡住的控件将恢复正常,且不再出现异常

Update 1 / 更新1

在使用中发现了控件改变后不能做出有效更新的问题。翻看了QBoxLayout等的源代码后发现里面多处使用到了invalidate()来标记重新计算布局。在源代码中增加其调用后表现正常

Markdown 中没有排版的概念,所有不要试图花费大力气去进行缩进等排版。各个Markdown渲染对缩进表达各不相同。