Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1b9c386

Browse files
committed
[Flutter GPU] Add missing MSAA stuff.
1 parent 3e15127 commit 1b9c386

File tree

8 files changed

+213
-38
lines changed

8 files changed

+213
-38
lines changed

lib/gpu/context.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,8 @@ extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment(
111111
flutter::gpu::Context* wrapper) {
112112
return impeller::DefaultUniformAlignment();
113113
}
114+
115+
extern bool InternalFlutterGpu_Context_GetSupportsOffscreenMSAA(
116+
flutter::gpu::Context* wrapper) {
117+
return wrapper->GetContext()->GetCapabilities()->SupportsOffscreenMSAA();
118+
}

lib/gpu/context.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ FLUTTER_GPU_EXPORT
7474
extern int InternalFlutterGpu_Context_GetMinimumUniformByteAlignment(
7575
flutter::gpu::Context* wrapper);
7676

77+
FLUTTER_GPU_EXPORT
78+
extern bool InternalFlutterGpu_Context_GetSupportsOffscreenMSAA(
79+
flutter::gpu::Context* wrapper);
80+
7781
} // extern "C"
7882

7983
#endif // FLUTTER_LIB_GPU_CONTEXT_H_

lib/gpu/lib/src/command_buffer.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ part of flutter_gpu;
99
typedef CompletionCallback<T> = void Function(bool success);
1010

1111
base class CommandBuffer extends NativeFieldWrapperClass1 {
12+
final GpuContext _gpuContext;
13+
1214
/// Creates a new CommandBuffer.
13-
CommandBuffer._(GpuContext gpuContext) {
14-
_initialize(gpuContext);
15+
CommandBuffer._(this._gpuContext) {
16+
_initialize(_gpuContext);
1517
}
1618

1719
RenderPass createRenderPass(RenderTarget renderTarget) {
18-
return RenderPass._(this, renderTarget);
20+
return RenderPass._(_gpuContext, this, renderTarget);
1921
}
2022

2123
void submit({CompletionCallback? completionCallback}) {

lib/gpu/lib/src/context.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ base class GpuContext extends NativeFieldWrapperClass1 {
4646
return _getMinimumUniformByteAlignment();
4747
}
4848

49+
/// Whether the backend supports multisample anti-aliasing for offscreen
50+
/// color and stencil attachments. A subset of OpenGLES-only devices do not
51+
/// support this functionality.
52+
///
53+
/// Any texture created via [createTexture] is an offscreen texture.
54+
/// There is currently no way to render directly against the "onscreen"
55+
/// texture that the framework renders to, so all Flutter GPU textures are
56+
/// "offscreen".
57+
bool get doesSupportOffscreenMSAA {
58+
return _getSupportsOffscreenMSAA();
59+
}
60+
4961
/// Allocates a new region of GPU-resident memory.
5062
///
5163
/// The [storageMode] must be either [StorageMode.hostVisible] or
@@ -134,6 +146,10 @@ base class GpuContext extends NativeFieldWrapperClass1 {
134146
@Native<Int Function(Pointer<Void>)>(
135147
symbol: 'InternalFlutterGpu_Context_GetMinimumUniformByteAlignment')
136148
external int _getMinimumUniformByteAlignment();
149+
150+
@Native<Bool Function(Pointer<Void>)>(
151+
symbol: 'InternalFlutterGpu_Context_GetSupportsOffscreenMSAA')
152+
external bool _getSupportsOffscreenMSAA();
137153
}
138154

139155
/// The default graphics context.

lib/gpu/lib/src/render_pass.dart

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,32 @@ base class ColorAttachment {
2121

2222
Texture texture;
2323
Texture? resolveTexture;
24+
25+
void _assertValid() {
26+
if (resolveTexture != null) {
27+
assert(resolveTexture!.format == texture.format,
28+
"ColorAttachment MSAA resolve texture must have the same format as the texture");
29+
assert(
30+
resolveTexture!.width == texture.width &&
31+
resolveTexture!.height == texture.height,
32+
"ColorAttachment MSAA resolve texture must have the same dimensions as the texture");
33+
assert(resolveTexture!.sampleCount == 1,
34+
"ColorAttachment MSAA resolve texture must have a sample count of 1");
35+
assert(texture.sampleCount > 1,
36+
"ColorAttachment must have a sample count greater than 1 when a MSAA resolve texture is set");
37+
assert(
38+
storeAction == StoreAction.multisampleResolve ||
39+
storeAction == StoreAction.storeAndMultisampleResolve,
40+
"ColorAttachment StoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set");
41+
assert(resolveTexture!.storageMode != StorageMode.deviceTransient,
42+
"ColorAttachment MSAA resolve texture must not have a storage mode of deviceTransient");
43+
}
44+
45+
assert(
46+
texture.storageMode != StorageMode.deviceTransient ||
47+
loadAction != LoadAction.load,
48+
"ColorAttachment loadAction must not be load when the texture has a storage mode of deviceTransient");
49+
}
2450
}
2551

2652
base class DepthStencilAttachment {
@@ -43,6 +69,43 @@ base class DepthStencilAttachment {
4369
int stencilClearValue;
4470

4571
Texture texture;
72+
Texture? resolveTexture;
73+
74+
void _assertValid() {
75+
if (resolveTexture != null) {
76+
assert(resolveTexture!.format == texture.format,
77+
"DepthStencilAttachment MSAA resolve texture must have the same format as the texture");
78+
assert(
79+
resolveTexture!.width == texture.width &&
80+
resolveTexture!.height == texture.height,
81+
"DepthStencilAttachment MSAA resolve texture must have the same dimensions as the texture");
82+
assert(resolveTexture!.sampleCount == 1,
83+
"DepthStencilAttachment MSAA resolve texture must have a sample count of 1");
84+
assert(texture.sampleCount > 1,
85+
"DepthStencilAttachment must have a sample count greater than 1 when a MSAA resolve texture is set");
86+
assert(
87+
depthStoreAction == StoreAction.multisampleResolve ||
88+
depthStoreAction == StoreAction.storeAndMultisampleResolve,
89+
"DepthStencilAttachment depthStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set");
90+
assert(
91+
stencilStoreAction == StoreAction.multisampleResolve ||
92+
stencilStoreAction == StoreAction.storeAndMultisampleResolve,
93+
"DepthStencilAttachment stencilStoreAction must be multisampleResolve or storeAndMultisampleResolve when a resolve texture is set");
94+
assert(resolveTexture!.storageMode != StorageMode.deviceTransient,
95+
"DepthStencilAttachment MSAA resolve texture must not have a storage mode of deviceTransient");
96+
}
97+
98+
if (texture.storageMode == StorageMode.deviceTransient) {
99+
assert(
100+
texture.storageMode != StorageMode.deviceTransient ||
101+
depthLoadAction != LoadAction.load,
102+
"DepthStencilAttachment depthLoadAction must not be load when the texture has a storage mode of deviceTransient");
103+
assert(
104+
texture.storageMode != StorageMode.deviceTransient ||
105+
stencilLoadAction != LoadAction.load,
106+
"DepthStencilAttachment stencilLoadAction must not be load when the texture has a storage mode of deviceTransient");
107+
}
108+
}
46109
}
47110

48111
base class StencilConfig {
@@ -117,6 +180,15 @@ base class RenderTarget {
117180
colorAttachments: [colorAttachment],
118181
depthStencilAttachment: depthStencilAttachment);
119182

183+
_assertValid() {
184+
for (final color in colorAttachments) {
185+
color._assertValid();
186+
}
187+
if (depthStencilAttachment != null) {
188+
depthStencilAttachment!._assertValid();
189+
}
190+
}
191+
120192
final List<ColorAttachment> colorAttachments;
121193
final DepthStencilAttachment? depthStencilAttachment;
122194
}
@@ -132,7 +204,10 @@ int _colorToInt(ui.Color color) {
132204

133205
base class RenderPass extends NativeFieldWrapperClass1 {
134206
/// Creates a new RenderPass.
135-
RenderPass._(CommandBuffer commandBuffer, RenderTarget renderTarget) {
207+
RenderPass._(GpuContext gpuContext, CommandBuffer commandBuffer,
208+
RenderTarget renderTarget) {
209+
renderTarget._assertValid();
210+
136211
_initialize();
137212
String? error;
138213
for (final (index, color) in renderTarget.colorAttachments.indexed) {
@@ -156,7 +231,8 @@ base class RenderPass extends NativeFieldWrapperClass1 {
156231
ds.stencilLoadAction.index,
157232
ds.stencilStoreAction.index,
158233
ds.stencilClearValue,
159-
ds.texture);
234+
ds.texture,
235+
ds.resolveTexture);
160236
if (error != null) {
161237
throw Exception(error);
162238
}
@@ -291,8 +367,8 @@ base class RenderPass extends NativeFieldWrapperClass1 {
291367
Texture? resolveTexture);
292368

293369
@Native<
294-
Handle Function(
295-
Pointer<Void>, Int, Int, Float, Int, Int, Int, Pointer<Void>)>(
370+
Handle Function(Pointer<Void>, Int, Int, Float, Int, Int, Int,
371+
Pointer<Void>, Handle)>(
296372
symbol: 'InternalFlutterGpu_RenderPass_SetDepthStencilAttachment')
297373
external String? _setDepthStencilAttachment(
298374
int depthLoadAction,
@@ -301,7 +377,8 @@ base class RenderPass extends NativeFieldWrapperClass1 {
301377
int stencilLoadAction,
302378
int stencilStoreAction,
303379
int stencilClearValue,
304-
Texture texture);
380+
Texture texture,
381+
Texture? resolveTexture);
305382

306383
@Native<Handle Function(Pointer<Void>, Pointer<Void>)>(
307384
symbol: 'InternalFlutterGpu_RenderPass_Begin')

lib/gpu/render_pass.cc

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,25 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment(
241241
int stencil_load_action,
242242
int stencil_store_action,
243243
int stencil_clear_value,
244-
flutter::gpu::Texture* texture) {
244+
flutter::gpu::Texture* texture,
245+
Dart_Handle resolve_texture_wrapper) {
246+
flutter::gpu::Texture* resolve_texture = nullptr;
247+
248+
if (!Dart_IsNull(resolve_texture_wrapper)) {
249+
flutter::gpu::Texture* resolve_texture =
250+
tonic::DartConverter<flutter::gpu::Texture*>::FromDart(
251+
resolve_texture_wrapper);
252+
}
253+
245254
{
246255
impeller::DepthAttachment desc;
247256
desc.load_action = flutter::gpu::ToImpellerLoadAction(depth_load_action);
248257
desc.store_action = flutter::gpu::ToImpellerStoreAction(depth_store_action);
249258
desc.clear_depth = depth_clear_value;
250259
desc.texture = texture->GetTexture();
260+
if (resolve_texture) {
261+
desc.resolve_texture = resolve_texture->GetTexture();
262+
}
251263
wrapper->GetRenderTarget().SetDepthAttachment(desc);
252264
}
253265
{
@@ -257,6 +269,9 @@ Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment(
257269
flutter::gpu::ToImpellerStoreAction(stencil_store_action);
258270
desc.clear_stencil = stencil_clear_value;
259271
desc.texture = texture->GetTexture();
272+
if (resolve_texture) {
273+
desc.resolve_texture = resolve_texture->GetTexture();
274+
}
260275
wrapper->GetRenderTarget().SetStencilAttachment(desc);
261276
}
262277

lib/gpu/render_pass.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ extern Dart_Handle InternalFlutterGpu_RenderPass_SetDepthStencilAttachment(
120120
int stencil_load_action,
121121
int stencil_store_action,
122122
int stencil_clear_value,
123-
flutter::gpu::Texture* texture);
123+
flutter::gpu::Texture* texture,
124+
Dart_Handle resolve_texture_wrapper);
124125

125126
FLUTTER_GPU_EXPORT
126127
extern Dart_Handle InternalFlutterGpu_RenderPass_Begin(

testing/dart/gpu_test.dart

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ RenderPassState createSimpleRenderPass() {
6060
final gpu.Texture? depthStencilTexture = gpu.gpuContext.createTexture(
6161
gpu.StorageMode.deviceTransient, 100, 100,
6262
format: gpu.gpuContext.defaultDepthStencilFormat);
63+
assert(depthStencilTexture != null);
6364

6465
final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer();
6566

@@ -74,6 +75,61 @@ RenderPassState createSimpleRenderPass() {
7475
return RenderPassState(renderTexture, commandBuffer, renderPass);
7576
}
7677

78+
RenderPassState createSimpleRenderPassWithMSAA() {
79+
// Create transient MSAA attachments, which will live entirely in tile memory
80+
// for most GPUs.
81+
82+
final gpu.Texture? renderTexture = gpu.gpuContext
83+
.createTexture(gpu.StorageMode.deviceTransient, 100, 100, sampleCount: 4);
84+
assert(renderTexture != null);
85+
86+
// Create the single-sample resolve texture that live in DRAM and will be
87+
// drawn to the screen.
88+
89+
final gpu.Texture? resolveTexture =
90+
gpu.gpuContext.createTexture(gpu.StorageMode.hostVisible, 100, 100);
91+
assert(resolveTexture != null);
92+
93+
final gpu.CommandBuffer commandBuffer = gpu.gpuContext.createCommandBuffer();
94+
95+
final gpu.RenderTarget renderTarget = gpu.RenderTarget.singleColor(
96+
gpu.ColorAttachment(
97+
texture: renderTexture!,
98+
resolveTexture: resolveTexture,
99+
storeAction: gpu.StoreAction.multisampleResolve));
100+
101+
final gpu.RenderPass renderPass =
102+
commandBuffer.createRenderPass(renderTarget);
103+
104+
return RenderPassState(resolveTexture!, commandBuffer, renderPass);
105+
}
106+
107+
void drawTriangle(RenderPassState state, Vector4 color) {
108+
final gpu.RenderPipeline pipeline = createUnlitRenderPipeline();
109+
110+
state.renderPass.bindPipeline(pipeline);
111+
112+
final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer();
113+
final gpu.BufferView vertices = transients.emplace(float32(<double>[
114+
-0.5, 0.5, //
115+
0.0, -0.5, //
116+
0.5, 0.5, //
117+
]));
118+
final gpu.BufferView vertInfoData =
119+
transients.emplace(unlitUBO(Matrix4.identity(), color));
120+
state.renderPass.bindVertexBuffer(vertices, 3);
121+
122+
// TODO(bdero): Overwrite bindings with the same slot so we don't need to clear.
123+
// https://github.com/flutter/flutter/issues/155335
124+
state.renderPass.clearBindings();
125+
126+
final gpu.UniformSlot vertInfo =
127+
pipeline.vertexShader.getUniformSlot('VertInfo');
128+
state.renderPass.bindUniform(vertInfo, vertInfoData);
129+
130+
state.renderPass.draw();
131+
}
132+
77133
void main() async {
78134
final ImageComparer comparer = await ImageComparer.create();
79135

@@ -327,40 +383,39 @@ void main() async {
327383
// Renders a green triangle pointing downwards.
328384
test('Can render triangle', () async {
329385
final state = createSimpleRenderPass();
330-
331-
final gpu.RenderPipeline pipeline = createUnlitRenderPipeline();
332-
state.renderPass.bindPipeline(pipeline);
333-
334-
// Configure blending with defaults (just to test the bindings).
335-
state.renderPass.setColorBlendEnable(true);
336-
state.renderPass.setColorBlendEquation(gpu.ColorBlendEquation());
337-
338-
final gpu.HostBuffer transients = gpu.gpuContext.createHostBuffer();
339-
final gpu.BufferView vertices = transients.emplace(float32(<double>[
340-
-0.5, 0.5, //
341-
0.0, -0.5, //
342-
0.5, 0.5, //
343-
]));
344-
final gpu.BufferView vertInfoData = transients.emplace(float32(<double>[
345-
1, 0, 0, 0, // mvp
346-
0, 1, 0, 0, // mvp
347-
0, 0, 1, 0, // mvp
348-
0, 0, 0, 1, // mvp
349-
0, 1, 0, 1, // color
350-
]));
351-
state.renderPass.bindVertexBuffer(vertices, 3);
352-
353-
final gpu.UniformSlot vertInfo =
354-
pipeline.vertexShader.getUniformSlot('VertInfo');
355-
state.renderPass.bindUniform(vertInfo, vertInfoData);
356-
state.renderPass.draw();
357-
386+
drawTriangle(state, Colors.lime);
358387
state.commandBuffer.submit();
359388

360389
final ui.Image image = state.renderTexture.asImage();
361390
await comparer.addGoldenImage(image, 'flutter_gpu_test_triangle.png');
362391
}, skip: !impellerEnabled);
363392

393+
// Renders a green triangle pointing downwards, with 4xMSAA.
394+
test('Can render triangle with MSAA', () async {
395+
final state = createSimpleRenderPassWithMSAA();
396+
drawTriangle(state, Colors.lime);
397+
state.commandBuffer.submit();
398+
399+
final ui.Image image = state.renderTexture.asImage();
400+
await comparer.addGoldenImage(image, 'flutter_gpu_test_triangle_msaa.png');
401+
}, skip: !(impellerEnabled && gpu.gpuContext.doesSupportOffscreenMSAA));
402+
403+
test(
404+
'Rendering with MSAA throws exception when offscreen MSAA is not supported',
405+
() async {
406+
try {
407+
final state = createSimpleRenderPassWithMSAA();
408+
drawTriangle(state, Colors.lime);
409+
state.commandBuffer.submit();
410+
fail('Exception not thrown when offscreen MSAA is not supported.');
411+
} catch (e) {
412+
expect(
413+
e.toString(),
414+
contains(
415+
'The backend does not support multisample anti-aliasing for offscreen color and stencil attachments'));
416+
}
417+
}, skip: gpu.gpuContext.doesSupportOffscreenMSAA);
418+
364419
// Renders a hollow green triangle pointing downwards.
365420
test('Can render hollowed out triangle using stencil ops', () async {
366421
final state = createSimpleRenderPass();

0 commit comments

Comments
 (0)