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

[Flutter GPU] Raster encoding. First triangle! #48314

Merged
merged 11 commits into from
Nov 25, 2023

Conversation

bdero
Copy link
Member

@bdero bdero commented Nov 22, 2023

First triangle, in the framework! 🎉

Screen.Recording.2023-11-22.at.6.04.13.AM.mov

Adds shader libraries, pipelines, command buffers, render passes, etc.

  • Light pipelines/shader objects. No optimization yet, pipeline warming to come.
  • "Dynamic" command style. Don't re-send bindings if you don't need to. Essentially: [Impeller] Make RenderPass commands more granular. flutter#133179
  • No need to explicitly encode passes.
  • Minimal descriptor usage.
  • Nothing is async, except for the optional command buffer completion callback.

It took a bunch of experimenting to get here, but I think things are starting to look pretty neat. :)

Todo:

  • Land the shader bundle format/remove the testing hacks & fixtures that piggyback off of the runtime effect system.
  • Add remaining calls for blend config, clearing bindings, etc.
  • Inconsistent error handling patterns that need cleanup.
  • Maybe: Surface exceptions for validation errors.
  • Handle the texture usage bitmask more elegantly.

This is the test code that's rendering this in the framework:

  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);

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

    /// Add a render pass encoder to the command buffer so that we can start
    /// encoding commands.
    final encoder = commandBuffer.createRenderPass(
        colorAttachment: gpu.ColorAttachment(texture: renderTexture));

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

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

    encoder.bindPipeline(pipeline);

    /// 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>[
      -0.5, -0.5, //
      0, 0.5, //
      0.5, -0.5, //
    ]));
    final color = transients.emplace(float32(<double>[0, 1, 0, 1])); // rgba
    final mvp = transients.emplace(float32Mat(Matrix4(
          1, 0, 0, 0, //
          0, 1, 0, 0, //
          0, 0, 1, 0, //
          0, 0, 0.5, 1, //
        ) *
        Matrix4.rotationX(time) *
        Matrix4.rotationY(time * seedX) *
        Matrix4.rotationZ(time * seedY)));

    /// Bind the vertex data. In this case, we won't bother binding an index
    /// buffer.
    encoder.bindVertexBuffer(vertices, 3);

    /// Bind the host buffer data we just created to the vertex shader's uniform
    /// slots. Although the locations are specified in the shader and are
    /// predictable, we can optionally fetch the uniform slots by name for
    /// convenience.
    final mvpSlot = pipeline.vertexShader.getUniformSlot('mvp')!;
    final colorSlot = pipeline.vertexShader.getUniformSlot('color')!;
    encoder.bindUniform(mvpSlot, mvp);
    encoder.bindUniform(colorSlot, color);

    /// 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());
  }

UnlitVertex:

uniform mat4 mvp;
uniform vec4 color;

in vec2 position;
out vec4 v_color;

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

UnlitFragment:

in vec4 v_color;
out vec4 frag_color;

void main() {
  frag_color = v_color;
}

@bdero bdero self-assigned this Nov 22, 2023
@bdero bdero marked this pull request as ready for review November 22, 2023 15:24
Copy link
Member

@zanderso zanderso left a comment

Choose a reason for hiding this comment

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

When we're comfortable with the API, there will need to be very thorough docstrings on the public API.

@@ -0,0 +1,124 @@
#include "flutter/lib/gpu/fixtures.h"
Copy link
Member

Choose a reason for hiding this comment

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

This file needs a copyright header, and a comment with instructions on how to regenerate the arrays below. Instead of hardcoding this file, consider generating at build time like in #46862.

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. This is a silly temp measure for provisioning a shader lib before I land the new shader bundle format.

@bdero
Copy link
Member Author

bdero commented Nov 22, 2023

When we're comfortable with the API, there will need to be very thorough docstrings on the public API.

Agreed. I'd like to continue foregoing docstrings for now while we feel out the rest of the API and have some representative examples that we're happy with.

@bdero bdero force-pushed the bdero/flutter-gpu-raster branch 2 times, most recently from f5aa61c to c108fb9 Compare November 23, 2023 00:28
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.

I'm having an increasing fear that added functionality here with light testing will make the refactoring work we'll need to do in the engine later this year and next much harder.

Comment on lines 100 to 107
enum ShaderStage {
unknown,
vertex,
fragment,
tessellationControl,
tessellationEvaluation,
compute,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we avoid exposing stages we don't have a plan for yet? I think this could just be vertex and fragment.

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.

ColorAttachment(
{this.loadAction = LoadAction.clear,
this.storeAction = StoreAction.store,
this.clearColor = const ui.Color(0),
Copy link
Contributor

Choose a reason for hiding this comment

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

uber-nit

Suggested change
this.clearColor = const ui.Color(0),
this.clearColor = const ui.Color(0x00000000),

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 93 to 112
switch (bufferView.buffer.runtimeType) {
case DeviceBuffer:
success = _bindUniformDevice(
slot.shaderStage.index,
slot.slotId,
bufferView.buffer as DeviceBuffer,
bufferView.offsetInBytes,
bufferView.lengthInBytes);
break;
case HostBuffer:
success = _bindUniformHost(
slot.shaderStage.index,
slot.slotId,
bufferView.buffer as HostBuffer,
bufferView.offsetInBytes,
bufferView.lengthInBytes);
break;
default:
throw Exception("Invalid buffer type");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Patterns like this indicate this should probably be a method on Buffer

Copy link
Member Author

Choose a reason for hiding this comment

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

Normally I'd agree, but this needs to dispatch to different native methods tied to the RenderPass handle. So then Buffer would end up having something like a reverse _bindTo(gpu.RenderPass, gpu.BufferView) that then calls back into the right RenderPass native method, which feels a lot less straight forward.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's a visitor pattern 🙂

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok ok, I'll bite. Done.

}

void bindVertexBuffer(BufferView bufferView, int vertexCount) {
switch (bufferView.buffer.runtimeType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

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 12 to 13
int slotId;
ShaderStage shaderStage;
Copy link
Contributor

Choose a reason for hiding this comment

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

Both of these fields feel like they should be final and the 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.


ShaderLibrary._();

Shader? operator [](String shaderName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: use a method for this. operators, getters, and [] feel cheap even when they aren't.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this is sufficiently cheap? It just grabs a handle to an already existing object on the C++ side.

@bdero
Copy link
Member Author

bdero commented Nov 23, 2023

I'm having an increasing fear that added functionality here with light testing will make the refactoring work we'll need to do in the engine later this year and next much harder.

Oh, it'll definitely break, but I think it's OK for Flutter GPU to break at this early stage while we're feeling out the API.

If you're working on API changes in the HAL, I actually think it would be acceptable to just remove the Flutter GPU target from the build script if fixing it causes too much friction, and I can come around later to fix it up.

@bdero bdero force-pushed the bdero/flutter-gpu-raster branch from c108fb9 to 40da2c0 Compare November 24, 2023 10:02
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Nov 25, 2023
…138995)

flutter/engine@b50fc4a...7743220

2023-11-25 bdero@google.com [Flutter GPU] Raster encoding. First triangle! (flutter/engine#48314)

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
harryterkelsen pushed a commit to harryterkelsen/engine that referenced this pull request Nov 27, 2023
First triangle, in the framework! 🎉 

Adds shader libraries, pipelines, command buffers, render passes, etc.

* Light pipelines/shader objects. No optimization yet, pipeline warming
to come.
* "Dynamic" command style. Don't re-send bindings if you don't need to.
Essentially: flutter/flutter#133179
* No need to explicitly encode passes.
* Minimal descriptor usage.
* Nothing is async, except for the optional command buffer completion
callback.

It took a bunch of experimenting to get here, but I think things are
starting to look pretty neat. :)

Todo:
* Land the shader bundle format/remove the testing hacks & fixtures that
piggyback off of the runtime effect system.
* Add remaining calls for blend config, clearing bindings, etc.
* Inconsistent error handling patterns that need cleanup.
* Maybe: Surface exceptions for validation errors.
* Handle the texture usage bitmask more elegantly.
caseycrogers pushed a commit to caseycrogers/flutter that referenced this pull request Dec 29, 2023
…lutter#138995)

flutter/engine@b50fc4a...7743220

2023-11-25 bdero@google.com [Flutter GPU] Raster encoding. First triangle! (flutter/engine#48314)

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