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

[Flutter GPU] Texture binding, index binding, attachments, depth state. #48386

Merged
merged 10 commits into from
Nov 27, 2023

Conversation

bdero
Copy link
Member

@bdero bdero commented Nov 26, 2023

Now rendering textured 3D models!

  • Combined depth+stencil attachment.
  • Allow for multiple color attachments.
  • Add blend mode configuration.
  • Fix uniform ordering for vertex shaders.
  • Texture binding and sampling options.
  • Index buffer binding.
  • Depth configuration.
Screen.Recording.2023-11-26.at.9.17.51.AM.mov
  void paint(Canvas canvas, Size size) {
    /// Allocate a new renderable texture.
    final gpu.Texture? renderTexture = gpu.gpuContext.createTexture(
        gpu.StorageMode.devicePrivate, 300, 300,
        enableRenderTargetUsage: true,
        enableShaderReadUsage: true,
        coordinateSystem: gpu.TextureCoordinateSystem.renderToTexture);
    if (renderTexture == null) {
      return;
    }

    final gpu.Texture? depthTexture = gpu.gpuContext.createTexture(
        gpu.StorageMode.deviceTransient, 300, 300,
        format: gpu.gpuContext.defaultDepthStencilFormat,
        enableRenderTargetUsage: true,
        coordinateSystem: gpu.TextureCoordinateSystem.renderToTexture);
    if (depthTexture == null) {
      return;
    }

    /// Create the command buffer. This will be used to submit all encoded
    /// commands at the end.
    final commandBuffer = gpu.gpuContext.createCommandBuffer();

    /// Define a render target. This is just a collection of attachments that a
    /// RenderPass will write to.
    final renderTarget = gpu.RenderTarget.singleColor(
      colorAttachment: gpu.ColorAttachment(texture: renderTexture),
      depthStencilAttachment: gpu.DepthStencilAttachment(
          texture: depthTexture, depthClearValue: 1.0),
    );

    /// Add a render pass encoder to the command buffer so that we can start
    /// encoding commands.
    final encoder = commandBuffer.createRenderPass(renderTarget);

    /// Load a shader bundle asset.
    final library = gpu.ShaderLibrary.fromAsset('TestLibrary')!;

    /// Create a RenderPipeline using shaders from the asset.
    final vertex = library['TextureVertex']!;
    final fragment = library['TextureFragment']!;
    final pipeline = gpu.gpuContext.createRenderPipeline(vertex, fragment);

    encoder.bindPipeline(pipeline);

    encoder.setDepthWriteEnable(true);
    encoder.setDepthCompareOperation(gpu.CompareFunction.less);

    /// (Optional) Configure blending for the first color attachment.
    encoder.setColorBlendEnable(true);
    encoder.setColorBlendEquation(gpu.ColorBlendEquation(
        colorBlendOperation: gpu.BlendOperation.add,
        sourceColorBlendFactor: gpu.BlendFactor.one,
        destinationColorBlendFactor: gpu.BlendFactor.oneMinusSourceAlpha,
        alphaBlendOperation: gpu.BlendOperation.add,
        sourceAlphaBlendFactor: gpu.BlendFactor.one,
        destinationAlphaBlendFactor: gpu.BlendFactor.oneMinusSourceAlpha));

    /// Append quick geometry and uniforms to a host buffer that will be
    /// automatically uploaded to the GPU later on.
    final transients = gpu.HostBuffer();
    final vertices = transients.emplace(float32(<double>[
      -1, -1, -1, /* */ 0, 0, /* */ 1, 0, 0, 1, //
      1, -1, -1, /*  */ 1, 0, /* */ 0, 1, 0, 1, //
      1, 1, -1, /*   */ 1, 1, /* */ 0, 0, 1, 1, //
      -1, 1, -1, /*  */ 0, 1, /* */ 0, 0, 0, 1, //
      -1, -1, 1, /*  */ 0, 0, /* */ 0, 1, 1, 1, //
      1, -1, 1, /*   */ 1, 0, /* */ 1, 0, 1, 1, //
      1, 1, 1, /*    */ 1, 1, /* */ 1, 1, 0, 1, //
      -1, 1, 1, /*   */ 0, 1, /* */ 1, 1, 1, 1, //
    ]));
    final indices = transients.emplace(uint16(<int>[
      0, 1, 3, 3, 1, 2, //
      1, 5, 2, 2, 5, 6, //
      5, 4, 6, 6, 4, 7, //
      4, 0, 7, 7, 0, 3, //
      3, 2, 7, 7, 2, 6, //
      4, 5, 0, 0, 5, 1, //
    ]));
    final mvp = transients.emplace(float32Mat(Matrix4(
          0.5, 0, 0, 0, //
          0, 0.5, 0, 0, //
          0, 0, 0.2, 0, //
          0, 0, 0.5, 1, //
        ) *
        Matrix4.rotationX(time) *
        Matrix4.rotationY(time * seedX) *
        Matrix4.rotationZ(time * seedY)));

    /// Bind the vertex and index buffer.
    encoder.bindVertexBuffer(vertices, 8);
    encoder.bindIndexBuffer(indices, gpu.IndexType.int16, 36);

    final mvpSlot = pipeline.vertexShader.getUniformSlot('mvp')!;
    encoder.bindUniform(mvpSlot, mvp);

    final sampledTexture = gpu.gpuContext.createTexture(
        gpu.StorageMode.hostVisible, 5, 5,
        enableShaderReadUsage: true);
    sampledTexture!.overwrite(uint32(<int>[
      0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, //
      0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, //
      0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, //
      0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, //
      0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, //
    ]));

    final texSlot = pipeline.fragmentShader.getUniformSlot('tex')!;
    encoder.bindTexture(texSlot, sampledTexture);

    /// And finally, we append a draw call.
    encoder.draw();

    /// Submit all of the previously encoded passes. Passes are encoded in the
    /// same order they were created in.
    commandBuffer.submit();

    /// Wrap the Flutter GPU texture as a ui.Image and draw it like normal!
    final image = renderTexture.asImage();

    canvas.drawImage(image, Offset(-renderTexture.width / 2, 0), Paint());
  }

TextureVertex

uniform mat4 mvp;

in vec3 position;
in vec2 texture_coords;
in vec4 color;
out vec2 v_texture_coords;
out vec4 v_color;

void main() {
  v_texture_coords = texture_coords;
  v_color = color;
  gl_Position = mvp * vec4(position, 1.0);
}

TextureFragment

uniform sampler2D tex;

in vec2 v_texture_coords;
in vec4 v_color;
out vec4 frag_color;

void main() {
  frag_color = v_color * texture(tex, v_texture_coords);
}

Copy link
Contributor

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nits on the Dart API

Overall I dislike the pattern of validating in the constructor of an Object via thrown exception. Not sure what the best alternative would be.

extern unsigned char kFlutterGPUTextureVertIPLR[];

constexpr unsigned int kFlutterGPUTextureFragIPLRLength = 800;
extern unsigned char kFlutterGPUTextureFragIPLR[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
extern unsigned char kFlutterGPUTextureFragIPLR[];
extern unsigned char kFlutterGPUTextureFragIPLR[];

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


base class RenderTarget {
RenderTarget(
{List<ColorAttachment>? colorAttachments, this.depthStencilAttachment}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{List<ColorAttachment>? colorAttachments, this.depthStencilAttachment}) {
{List<ColorAttachment> colorAttachments = const <ColorAttachment>[], this.depthStencilAttachment}) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Nice one!

Comment on lines 86 to 90
if (colorAttachments != null) {
colorAttachments = colorAttachments;
return;
}
colorAttachments = <ColorAttachment>[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (colorAttachments != null) {
colorAttachments = colorAttachments;
return;
}
colorAttachments = <ColorAttachment>[];

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 102 to 103
late List<ColorAttachment> colorAttachments;
DepthStencilAttachment? depthStencilAttachment;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like these should be final and the overall class const

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I converted RenderTarget.singleColor into a non-const factory. I think that's necessary, but let me know if I'm missing some const magic.

Comment on lines 95 to 98
return new RenderTarget(
colorAttachments: colors,
depthStencilAttachment: depthStencilAttachment);
}
Copy link
Contributor

@jonahwilliams jonahwilliams Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Dart's limited const expression support you won't be able to make this const, but it doesn't need to be a factory either.

It seems odd that the colorAttachment argument to this object is nullable?

Suggested change
return new RenderTarget(
colorAttachments: colors,
depthStencilAttachment: depthStencilAttachment);
}
return RenderTarget(
colorAttachments: [
if (colorAttachment != null)
colorAttachment
],
depthStencilAttachment: depthStencilAttachment);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah I guess there's no point in making this nullable. Switched back to constructor.

Comment on lines 91 to 94
List<ColorAttachment> colors = [];
if (colorAttachment != null) {
colors.add(colorAttachment);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
List<ColorAttachment> colors = [];
if (colorAttachment != null) {
colors.add(colorAttachment);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@bdero bdero force-pushed the bdero/flutter-gpu-sampling branch from 4c21321 to 9de4662 Compare November 27, 2023 02:38
@bdero bdero force-pushed the bdero/flutter-gpu-sampling branch from 9de4662 to e831520 Compare November 27, 2023 02:54
@bdero bdero merged commit 292a921 into flutter:main Nov 27, 2023
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Nov 27, 2023
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Nov 27, 2023
…139037)

flutter/engine@ebebb25...292a921

2023-11-27 bdero@google.com [Flutter GPU] Texture binding, index binding, attachments, depth state. (flutter/engine#48386)
2023-11-27 jonahwilliams@google.com [Impeller] use spec constant for decal support in morph filter. (flutter/engine#48288)
2023-11-27 jonahwilliams@google.com [Impeller] OES extension does not apply to regular textures for decal support (flutter/engine#48388)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC jacksongardner@google.com,rmistry@google.com,zra@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@zanderso
Copy link
Member

Overall I dislike the pattern of validating in the constructor of an Object via thrown exception. Not sure what the best alternative would be.

A pattern that I like better (but don't necessarily love) is to have a factory constructor return an "invalid" instance that stashes a description of the error state somewhere when something goes wrong during construction. Sort of like this: https://github.com/flutter/engine/blob/main/tools/pkg/engine_build_configs/lib/src/build_config.dart#L92. Then the caller can decide whether to throw an exception after checking obj.valid or whatever.

@jonahwilliams
Copy link
Contributor

I think it would be more reasonable to throw expceptions than to use an isValid bit. My problem with the valid bit is that it is easy to ignore/forget, and then the developer only gets the error when the invalid object is passed to a different method instead of at the original invalidation.

I would also investigate how WebGPU does validation for render target/attachment descriptions.

harryterkelsen pushed a commit to harryterkelsen/engine that referenced this pull request Nov 27, 2023
…e. (flutter#48386)

Now rendering textured 3D models!

* Combined depth+stencil attachment.
* Allow for multiple color attachments.
* Add blend mode configuration.
* Fix uniform ordering for vertex shaders.
* Texture binding and sampling options.
* Index buffer binding.
* Depth configuration.
caseycrogers pushed a commit to caseycrogers/flutter that referenced this pull request Dec 29, 2023
…lutter#139037)

flutter/engine@ebebb25...292a921

2023-11-27 bdero@google.com [Flutter GPU] Texture binding, index binding, attachments, depth state. (flutter/engine#48386)
2023-11-27 jonahwilliams@google.com [Impeller] use spec constant for decal support in morph filter. (flutter/engine#48288)
2023-11-27 jonahwilliams@google.com [Impeller] OES extension does not apply to regular textures for decal support (flutter/engine#48388)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC jacksongardner@google.com,rmistry@google.com,zra@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@bdero
Copy link
Member Author

bdero commented Jan 3, 2024

This is part of the Flutter GPU MVP umbrella bug: flutter/flutter#130921

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Archived in project
Development

Successfully merging this pull request may close these issues.

3 participants