8.0 KiB
可以实现,而且你这个“天然分区并行”的场景很适合这么做。但要把边界说清楚:这不是 Blend2D 官方提供的 BLImageView 功能,而是利用
BLImage::create_from_data() 给同一块外部 framebuffer 建多个独立的 BLImage 包装。Blend2D 官方明确支持外部
pixel_data + stride 创建 image。 (Blend2D)
比如最终 framebuffer 是 1920×1080 PRGB32:
struct Framebuffer {
uint8_t* data;
int width;
int height;
intptr_t stride;
};
BLImage make_view(
Framebuffer& fb,
int x,
int y,
int width,
int height)
{
BLImage image;
uint8_t* ptr = fb.data + y * fb.stride + x * 4;
image.create_from_data(
width,
height,
BL_FORMAT_PRGB32,
ptr,
fb.stride,
BL_DATA_ACCESS_RW);
return image;
}
比如:
1920 × 1080 framebuffer
┌──────────────┬──────────────┐
│ view A │ view B │
│ 960×540 │ 960×540 │
├──────────────┼──────────────┤
│ view C │ view D │
│ 960×540 │ 960×540 │
└──────────────┴──────────────┘
然后:
auto a = make_view(fb, 0, 0, 960, 540);
auto b = make_view(fb, 960, 0, 960, 540);
auto c = make_view(fb, 0, 540, 960, 540);
auto d = make_view(fb, 960, 540, 960, 540);
Taskflow:
worker 0 → BLContext(a)
worker 1 → BLContext(b)
worker 2 → BLContext(c)
worker 3 → BLContext(d)
四个 context 都可以是同步单线程 context:
BLContext ctx(a);
Blend2D 文档说明这种普通 BLContext(image) 就是 single-threaded synchronous rendering。 (Blend2D)
最关键的是为什么它可以绕开“一个 image 只能有一个 renderer”。
因为 Blend2D 的 exclusive-access 管理针对的是它自己的 image data 对象。BLContext::begin() 会增加目标 image 的 writer
count,并保证同一个 image data 不被另一个 renderer 同时使用。 (Blend2D)
而你这里创建的是:
BLImage A → external-memory wrapper A
BLImage B → external-memory wrapper B
BLImage C → external-memory wrapper C
它们不是:
BLImage b = a;
这种 weak copy。
每次都是独立调用:
create_from_data(...)
所以 Blend2D 看到的是三个不同 image data 对象。
只是:
物理内存:
A.data ─┐
B.data ─┼→ 同一个 framebuffer allocation
C.data ─┘
Blend2D并不知道这些外部内存背后属于同一 allocation。
因此真正的线程安全责任就在你这里。
你必须保证:
A 实际写入地址集合
∩
B 实际写入地址集合
=
∅
如果做到这一点,从 C++ 内存访问角度就是正常的并行写不同内存。
你需要特别注意几个问题。
第一,stride 必须是父 framebuffer 的 stride。
例如:
parent width = 1920
PRGB32 = 4 bytes
stride = 7680
B 虽然只有 960 宽:
pixel_data = base + 960 * 4;
但:
stride = 1920 * 4;
仍然不能写成:
stride = 960 * 4;
否则下一行就跑错位置。
BLImageData 本身也是这么定义的:pixel_data 是图像左上角,stride 是每行之间的字节跨度。 (Blend2D)
第二,view 范围必须真的落在 framebuffer 内:
x >= 0
y >= 0
x + width <= framebuffer.width
y + height <= framebuffer.height
这个检查我会放在你创建 surface/view 的最外层,而不是每次绘制都检查。
第三,也是比较重要的:不要让每个 view 都释放同一个 framebuffer。
create_from_data() 有:
destroy_func
user_data
用于外部内存生命周期管理。 (Blend2D)
如果你这样干:
view A destroy → free(framebuffer)
view B destroy → free(framebuffer)
就是 double-free。
你这种结构最简单的是:
image.create_from_data(
...,
BL_DATA_ACCESS_RW,
nullptr,
nullptr);
由你的 framebuffer owner 自己负责生命周期:
Framebuffer
owns memory
├── ImageView A
├── ImageView B
├── ImageView C
└── ImageView D
保证:
所有 ImageView / BLContext 销毁
↓
Framebuffer 才销毁
如果必须跨生命周期,就自己搞共享 owner/refcount。
第四,不要 weak-copy view 再期待两个 renderer 并行:
BLImage a = make_view(...);
BLImage b = a;
这不是两个 view。
这是共享同一个 Blend2D image data。Blend2D 的 BLImage copy 是 weak-copy,会共享底层 image data。 (Blend2D)
应该每个 region 独立:
BLImage a;
a.create_from_data(...);
BLImage b;
b.create_from_data(...);
第五,我建议你的 view 自己建立局部坐标系。
比如 Plot B 在最终 framebuffer:
x = 960
y = 0
w = 960
h = 540
但是它拿到的 BLImage 是:
0 .. 959
0 .. 539
所以 Plot 内部正常画:
ctx.fill_rect(BLRect(0, 0, 960, 540), ...);
不需要知道自己在父 framebuffer 里是 (960, 0)。
这对你的 Scene/Plot 抽象非常干净:
Parent framebuffer coordinates
↓
Surface/View
↓
local coordinates
↓
Plot renderer
还有一个容易担心但实际上比较好的点:Blend2D 会裁剪到当前 image target。
所以一个 960×540 的 view,即使你的 path 坐标跑到:
x = 1500
Blend2D target 本身只有:
960 × 540
正常 raster 不应该越过 target memory 定义的逻辑宽度去写相邻区域。
也就是说 view 本身实际上同时给了你一个天然 clip boundary。
所以我会把你的最终结构做成:
class Surface {
public:
BLImage image;
int x;
int y;
int width;
int height;
};
然后:
Framebuffer
│
├── Surface Plot0
│ └── BLImage(external framebuffer region)
│
├── Surface Plot1
│ └── BLImage(external framebuffer region)
│
└── Surface Plot2
└── BLImage(external framebuffer region)
Taskflow:
framebuffer
┌──────────┬──────────┐
│ Plot A │ Plot B │
├──────────┼──────────┤
│ Plot C │ Plot D │
└──────────┴──────────┘
▲ ▲
│ │
Task A Task B
这种情况下不存在:
Image A
Image B
Image C
↓
composite
每个任务第一次 rasterize 就已经直接落到最终 framebuffer。
不过我要保留一个工程上的判断:
这是利用 Blend2D external-image API 实现的物理 framebuffer 分区,并不是 Blend2D 文档明确承诺的“多个 external BLImage
指向同一 allocation 的不同区域并发渲染”高级用法。
Blend2D 官方承诺的是 external memory image 本身可用,以及一个 context 对其 image data 独占;它不会理解你几个独立 external image 的 backing allocations 是否重叠。 (Blend2D)
所以我会把它当成:
可实现
+
原理成立
+
需要你自己严格保证 region 不重叠
+
最好做一次 TSAN + 压力测试 + framebuffer guard 测试
对于你这种“一个大图天然分成多个 Plot”的场景,我认为值得做,而且比“各画一张临时图最后再 blit”更符合你追求后台性能的方向。