fix(vk): Use rasterOrdering mode on Imagination GPUs (#11072) 69b2a3c643

We already have a codepath that enables rasterOrdering on ARM, even
if the extension isn't present, because we know that's how these GPUs
work. If we enable this codepath on Imagination, it appears to work as
well. This works around an MSAA crash on Pixel 10.

feat(wgpu): Add core support for MSAA (#11040) cb9968caef
Implement an MSAA mode in WebGPU that doesn't rely on any optional
(non-core) features besides SPIR-V. This will be the catch-all fallback
that works everywhere.

Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
Co-authored-by: blakdragan7 <jcopela4@gmail.com>
This commit is contained in:
csmartdalton
2025-11-18 23:28:42 +00:00
parent 121dd6de5a
commit d908c5da4f
21 changed files with 1223 additions and 626 deletions

View File

@@ -1 +1 @@
81f6b8ffa7278517623faeafeaa93569dcbc3640
69b2a3c6431ea5cd460654c2c1fb997a9a55fc7f

View File

@@ -1248,6 +1248,7 @@ struct FlushDescriptor
// List of draws in the main render pass. These are rendered directly to the
// renderTarget.
const BlockAllocatedLinkedList<DrawBatch>* drawList = nullptr;
const DrawBatch* firstDstBlendBarrier = nullptr;
};
// Returns the area of the (potentially non-rectangular) quadrilateral that

View File

@@ -794,7 +794,10 @@ private:
gpu::FlushDescriptor m_flushDesc;
BlockAllocatedLinkedList<DrawBatch> m_drawList;
const DrawBatch** m_nextDstBlendBarrier = nullptr;
const DrawBatch* m_firstDstBlendBarrier;
// Final "next" pointer in the list of DrawBatches that have dstBlend
// barriers.
const DrawBatch** m_dstBlendBarrierListTail;
gpu::ShaderFeatures m_combinedShaderFeatures;

View File

@@ -90,17 +90,41 @@ private:
// Create a standard PLS "draw" pipeline for the current implementation.
wgpu::RenderPipeline makeDrawPipeline(rive::gpu::DrawType,
gpu::InterlockMode,
gpu::ShaderMiscFlags,
wgpu::TextureFormat framebufferFormat,
wgpu::ShaderModule vertexShader,
wgpu::ShaderModule fragmentShader);
wgpu::ShaderModule fragmentShader,
const gpu::PipelineState&);
// Create a standard PLS "draw" render pass for the current implementation.
wgpu::RenderPassEncoder makePLSRenderPass(wgpu::CommandEncoder,
const RenderTargetWebGPU*,
wgpu::LoadOp,
const wgpu::Color& clearColor,
bool fixedFunctionColorOutput);
// Begin a Rive render pass that uses pixel local storage.
wgpu::RenderPassEncoder beginPLSRenderPass(wgpu::CommandEncoder,
const FlushDescriptor&);
// Specifies how to load MSAA color/depth/stencil attachments when beginning
// an MSAA render pass.
enum class MSAABeginType : bool
{
primary,
restartAfterDstCopy,
};
// Specifies how to store MSAA color/depth/stencil attachments when ending
// an MSAA render pass.
enum class MSAAEndType : bool
{
finish,
breakForDstCopy,
};
// Begin a Rive render pass that uses MSAA.
wgpu::RenderPassEncoder beginMSAARenderPass(wgpu::CommandEncoder,
MSAABeginType,
MSAAEndType,
const FlushDescriptor&);
// Set the initial render pass state for draws (viewport, bindings, etc.).
void initDrawRenderPass(wgpu::RenderPassEncoder, const FlushDescriptor&);
wgpu::PipelineLayout drawPipelineLayout() const
{
@@ -134,7 +158,7 @@ private:
constexpr static int COLOR_RAMP_BINDINGS_COUNT = 1;
constexpr static int TESS_BINDINGS_COUNT = 6;
constexpr static int ATLAS_BINDINGS_COUNT = 7;
constexpr static int DRAW_BINDINGS_COUNT = 10;
constexpr static int DRAW_BINDINGS_COUNT = 11;
std::array<wgpu::BindGroupLayoutEntry, DRAW_BINDINGS_COUNT>
m_perFlushBindingLayouts;
@@ -174,7 +198,7 @@ private:
// Draw paths and image meshes using the gradient and tessellation textures.
class DrawPipeline;
std::map<uint32_t, DrawPipeline> m_drawPipelines;
std::map<uint64_t, DrawPipeline> m_drawPipelines;
wgpu::BindGroupLayout m_drawBindGroupLayouts[4 /*BINDINGS_SET_COUNT*/];
wgpu::Sampler m_linearSampler;
wgpu::Sampler m_imageSamplers[ImageSampler::MAX_SAMPLER_PERMUTATIONS];
@@ -188,9 +212,9 @@ private:
wgpu::Texture m_featherTexture;
wgpu::TextureView m_featherTextureView;
// Bound when there is not an image paint.
wgpu::Texture m_nullImagePaintTexture;
wgpu::TextureView m_nullImagePaintTextureView;
// Bound when a texture binding is unused.
wgpu::Texture m_nullTexture;
wgpu::TextureView m_nullTextureView;
};
class RenderTargetWebGPU : public RenderTarget
@@ -201,6 +225,8 @@ public:
return m_framebufferFormat;
}
wgpu::Texture targetTexture() const { return m_targetTexture; };
wgpu::TextureView targetTextureView() const { return m_targetTextureView; };
void setTargetTextureView(wgpu::TextureView, wgpu::Texture);
protected:
@@ -210,20 +236,42 @@ protected:
uint32_t width,
uint32_t height);
// Lazily loaded render pass resources.
wgpu::TextureView coverageTextureView();
wgpu::TextureView clipTextureView();
wgpu::TextureView scratchColorTextureView();
wgpu::TextureView msaaColorTextureView();
wgpu::TextureView msaaDepthStencilTextureView();
wgpu::Texture dstColorTexture();
wgpu::TextureView dstColorTextureView();
// Copies a sub-rectangle of the targetTexture to the dstColorTexture for
// shader blending. Shaders can't read from the targetTexture itself because
// it's also the resolveTarget.
void copyTargetToDstColorTexture(wgpu::CommandEncoder, IAABB dstReadBounds);
private:
friend class RenderContextWebGPUImpl;
const wgpu::Device m_device;
const wgpu::TextureFormat m_framebufferFormat;
wgpu::TextureUsage m_transientPLSUsage;
wgpu::Texture m_targetTexture;
wgpu::Texture m_coverageTexture;
wgpu::Texture m_clipTexture;
wgpu::Texture m_scratchColorTexture;
wgpu::Texture m_msaaColorTexture;
wgpu::Texture m_msaaDepthStencilTexture;
wgpu::Texture m_dstColorTexture;
wgpu::TextureView m_targetTextureView;
wgpu::TextureView m_coverageTextureView;
wgpu::TextureView m_clipTextureView;
wgpu::TextureView m_scratchColorTextureView;
wgpu::TextureView m_msaaColorTextureView;
wgpu::TextureView m_msaaDepthStencilTextureView;
wgpu::TextureView m_dstColorTextureView;
};
class TextureWebGPUImpl : public Texture

View File

@@ -9,6 +9,7 @@ namespace rive::gpu
{
class RenderContextGLImpl;
class RenderContextVulkanImpl;
class RenderContextWebGPUImpl;
} // namespace rive::gpu
struct FiddleContextOptions
@@ -45,6 +46,10 @@ public:
{
return nullptr;
}
virtual rive::gpu::RenderContextWebGPUImpl* renderContextWebGPUImpl() const
{
return nullptr;
}
virtual rive::gpu::RenderTarget* renderTargetOrNull() = 0;
virtual void onSizeChanged(GLFWwindow*,

View File

@@ -105,6 +105,11 @@ public:
return m_renderContext.get();
}
rive::gpu::RenderContextWebGPUImpl* renderContextWebGPUImpl() const final
{
return m_renderContext->static_impl_cast<RenderContextWebGPUImpl>();
}
rive::gpu::RenderTarget* renderTargetOrNull() override
{
return m_renderTarget.get();
@@ -247,11 +252,10 @@ public:
wgpuSurfaceConfigure(m_surface.Get(), &surfaceConfig);
m_surfaceIsConfigured = true;
m_renderTarget =
m_renderContext->static_impl_cast<RenderContextWebGPUImpl>()
->makeRenderTarget(wgpu::TextureFormat::RGBA8Unorm,
width,
height);
m_renderTarget = renderContextWebGPUImpl()->makeRenderTarget(
wgpu::TextureFormat::RGBA8Unorm,
width,
height);
m_pixelReadBuff = {};
}

View File

@@ -2155,8 +2155,10 @@ void RenderContextGLImpl::flush(const FlushDescriptor& desc)
draw = draw->nextDstRead())
{
assert(draw->blendMode() != BlendMode::srcOver);
glutils::BlitFramebuffer(draw->pixelBounds(),
renderTarget->height());
glutils::BlitFramebuffer(
desc.renderTargetUpdateBounds.intersect(
draw->pixelBounds()),
renderTarget->height());
}
renderTarget->bindMSAAFramebuffer(this, desc.msaaSampleCount);
}

View File

@@ -229,7 +229,8 @@ void RenderContext::LogicalFlush::rewind()
m_flushDesc = FlushDescriptor();
m_drawList.reset();
m_nextDstBlendBarrier = nullptr;
m_firstDstBlendBarrier = nullptr;
m_dstBlendBarrierListTail = &m_firstDstBlendBarrier;
m_combinedShaderFeatures = gpu::ShaderFeatures::NONE;
m_currentPathID = 0;
@@ -1577,22 +1578,30 @@ void RenderContext::LogicalFlush::writeResources()
// draw the old renderTarget contents into the framebuffer at the
// beginning of the render pass when
// LoadAction::preserveRenderTarget is specified.
m_drawList.emplace_back(m_ctx->perFrameAllocator(),
gpu::DrawType::renderPassInitialize,
m_baselineShaderMiscFlags,
gpu::DrawContents::opaquePaint,
1,
0,
BlendMode::srcOver,
ImageSampler::LinearClamp(),
// The MSAA init reads the framebuffer, so
// it needs the equivalent of a "dstBlend"
// barrier.
BarrierFlags::dstBlend);
m_drawList.emplace_back(
m_ctx->perFrameAllocator(),
gpu::DrawType::renderPassInitialize,
m_baselineShaderMiscFlags,
gpu::DrawContents::opaquePaint,
1,
0,
// A more realistic value here would be "BlendMode::none" (which
// is what actually happens), but since that isn't a Rive blend
// mode, we just need any value here. It will get ignored by the
// draw.
BlendMode::srcOver,
ImageSampler{.filter = ImageFilter::bilinear},
// The MSAA init reads the framebuffer, so it needs the
// equivalent of a "dstBlend" barrier.
BarrierFlags::dstBlend);
m_combinedDrawContents |= m_drawList.tail()->drawContents;
// The draw that follows the this init will need a special
// "msaaPostInit" barrier.
m_pendingBarriers |= BarrierFlags::msaaPostInit;
assert(m_dstBlendBarrierListTail == &m_firstDstBlendBarrier);
assert(m_firstDstBlendBarrier == nullptr);
m_firstDstBlendBarrier = m_drawList.tail();
m_dstBlendBarrierListTail = &m_drawList.tail()->nextDstBlendBarrier;
}
// Find a mask that tells us when to insert barriers, and which barriers
@@ -1831,6 +1840,7 @@ void RenderContext::LogicalFlush::writeResources()
initialTriangleVertexDataSize;
m_flushDesc.drawList = &m_drawList;
m_flushDesc.firstDstBlendBarrier = m_firstDstBlendBarrier;
// Write out the uniforms for this flush now that the flushDescriptor is
// complete.
@@ -3213,24 +3223,23 @@ gpu::DrawBatch& RenderContext::LogicalFlush::pushDraw(
// that barrier for the first subpass is all we need.)
if (draw->nextDstRead() == nullptr)
{
batch->barriers |= BarrierFlags::dstBlend;
batch->dstReadList = draw->addToDstReadList(batch->dstReadList);
if (m_nextDstBlendBarrier != nullptr)
assert(m_dstBlendBarrierListTail != nullptr);
if (!(batch->barriers & BarrierFlags::dstBlend))
{
*m_nextDstBlendBarrier = batch;
batch->barriers |= BarrierFlags::dstBlend;
// Add ourselves to the "dstBlendBarrier" list.
assert(*m_dstBlendBarrierListTail == nullptr);
*m_dstBlendBarrierListTail = batch;
assert(batch->nextDstBlendBarrier == nullptr);
m_dstBlendBarrierListTail = &batch->nextDstBlendBarrier;
}
m_nextDstBlendBarrier = &batch->nextDstBlendBarrier;
// We either added ourselves to the dstBlendBarrier list or
// merged into a batch that was already part of it.
assert(m_dstBlendBarrierListTail ==
&batch->nextDstBlendBarrier);
}
}
// The first batch in a drawList is also the head of the
// "nextDstBlendBarrier" sub-list, regardless of whether it has a
// dstBlend barrier of its own. (But after this first batch, only
// batches with a dstBlend barrier will participate.)
if (m_nextDstBlendBarrier == nullptr)
{
m_nextDstBlendBarrier = &batch->nextDstBlendBarrier;
}
}
m_combinedShaderFeatures |= batch->shaderFeatures;

View File

@@ -175,26 +175,12 @@ SPIRV_FIXEDCOLOR_FRAG_INPUTS := \
spirv/draw_msaa_path.main \
spirv/draw_msaa_stencil.main \
# Files that need separate vertex shaders that are built with DISABLE_CLIP_RECT_FOR_VULKAN_MSAA
SPIRV_NOCLIPDISTANCE_VERT_INPUTS := \
# MSAA vertex and fragment shaders are built multiple times with different flags.
SPIRV_DRAW_MSAA_INPUTS := \
spirv/draw_msaa_path.main \
spirv/draw_msaa_image_mesh.main \
spirv/draw_msaa_atlas_blit.main \
# Files that need separate vertex shaders that are compatible with WebGPU
SPIRV_WEBGPU_VERT_INPUTS := \
spirv/draw_path.main \
spirv/draw_interior_triangles.main \
spirv/draw_atlas_blit.main \
# Files that need separate fragment shaders that are compatible with WebGPU
SPIRV_WEBGPU_FRAG_INPUTS := \
spirv/draw_path.main \
spirv/draw_interior_triangles.main \
spirv/draw_atlas_blit.main \
spirv/draw_image_mesh.main \
## Helpers for use in SPIRV_LIST_RULE (using lower_snake_case to distinguish things that use inputs like $1, $2, etc)
spirv_typed_filename = $(basename $1).$2
@@ -350,9 +336,11 @@ $(eval $(call make_spirv_rules, $(SPIRV_STANDARD_INPUTS), vert frag))
## Each of the specialized SPIRV lists have their own associated rules
$(eval $(call make_spirv_rules, $(SPIRV_FIXEDCOLOR_FRAG_INPUTS), fixedcolor_frag, -DFIXED_FUNCTION_COLOR_OUTPUT))
$(eval $(call make_spirv_rules, $(SPIRV_NOCLIPDISTANCE_VERT_INPUTS), noclipdistance_vert, -DDISABLE_CLIP_RECT_FOR_VULKAN_MSAA))
$(eval $(call make_spirv_rules, $(SPIRV_WEBGPU_VERT_INPUTS), webgpu_vert, -DSPEC_CONST_NONE))
$(eval $(call make_spirv_rules, $(SPIRV_WEBGPU_FRAG_INPUTS), webgpu_frag, -DSPEC_CONST_NONE))
$(eval $(call make_spirv_rules, $(SPIRV_DRAW_MSAA_INPUTS), noclipdistance_vert, -DDISABLE_CLIP_DISTANCE_FOR_UBERSHADERS))
$(eval $(call make_spirv_rules, $(SPIRV_DRAW_MSAA_INPUTS), webgpu_vert, -DSPEC_CONST_NONE))
$(eval $(call make_spirv_rules, $(SPIRV_DRAW_MSAA_INPUTS), webgpu_noclipdistance_vert, -DSPEC_CONST_NONE -DDISABLE_CLIP_DISTANCE_FOR_UBERSHADERS))
$(eval $(call make_spirv_rules, $(SPIRV_DRAW_MSAA_INPUTS), webgpu_frag, -DSPEC_CONST_NONE -DINPUT_ATTACHMENT_NONE))
$(eval $(call make_spirv_rules, $(SPIRV_DRAW_MSAA_INPUTS), webgpu_fixedcolor_frag, -DSPEC_CONST_NONE -DINPUT_ATTACHMENT_NONE -DFIXED_FUNCTION_COLOR_OUTPUT))
spirv: $(SPIRV_OUTPUTS_HEADERS)

View File

@@ -150,6 +150,15 @@ half3 advanced_blend_coeffs(half3 src, half4 dstPremul, ushort mode)
half3 coeffs;
switch (mode)
{
#if defined(@RENDER_MODE_MSAA) && defined(@SPEC_CONST_NONE)
// Normally MSAA filters out the "BLEND_SRC_OVER" draws via
// specialization constants or #ifdefs. But when specialization
// constants are disabled for WebGPU, we need to handle it here in the
// switch.
case BLEND_SRC_OVER:
coeffs = src;
break;
#endif
case BLEND_MODE_MULTIPLY:
coeffs = src.rgb * dst.rgb;
break;

View File

@@ -326,6 +326,13 @@ INLINE void set_clip_rect_plane_distances(float2x2 clipRectInverseMatrix,
float2 pixelPosition
CLIP_CONTEXT_FORWARD)
{
// MSAA uses gl_ClipDistance when ENABLE_CLIP_RECT is set, but since SPIRV uses
// specialization constants (as opposed to compile-time flags), it means that
// the usage of them is in the compiled shader even if that codepath is not
// going to be taken, which ends up as a validation failure on systems that do
// not support that extension. In those cases, we compile separate SPIRV
// binaries with gl_ClipDistance explicitly disabled.
#ifndef @DISABLE_CLIP_DISTANCE_FOR_UBERSHADERS
if (any(notEqual(float4(clipRectInverseMatrix), float4(.0, .0, .0, .0))))
{
float2 clipRectCoord = MUL(clipRectInverseMatrix, pixelPosition) +
@@ -343,6 +350,7 @@ INLINE void set_clip_rect_plane_distances(float2x2 clipRectInverseMatrix,
gl_ClipDistance[0] = gl_ClipDistance[1] = gl_ClipDistance[2] =
gl_ClipDistance[3] = clipRectInverseTranslate.x - .5;
}
#endif // !@DISABLE_CLIP_DISTANCE_FOR_UBERSHADERS
}
#endif // ENABLE_CLIP_RECT

View File

@@ -51,26 +51,29 @@ FRAG_DATA_MAIN(half4, @drawFragmentMain)
half4 color = find_paint_color(v_paint, coverage FRAGMENT_CONTEXT_UNPACK);
#endif
#if defined(@ENABLE_ADVANCED_BLEND) && !defined(@FIXED_FUNCTION_COLOR_OUTPUT)
#ifdef @ENABLE_ADVANCED_BLEND
if (@ENABLE_ADVANCED_BLEND)
{
#ifndef @FIXED_FUNCTION_COLOR_OUTPUT
// Do the color portion of the blend mode in the shader.
#ifdef @DRAW_IMAGE_MESH
color.rgb = unmultiply_rgb(color);
ushort blendMode = cast_uint_to_ushort(imageDrawUniforms.blendMode);
#else // if !@DRAW_IMAGE_MESH
// NOTE: for non-image-meshes, "color" is already unmultiplied because
// GENERATE_PREMULTIPLIED_PAINT_COLORS is false when using advanced
// blend.
#else
// NOTE: for non-image-meshes, "color" is already unmultiplied because
// GENERATE_PREMULTIPLIED_PAINT_COLORS is false when using advanced
// blend.
ushort blendMode = cast_half_to_ushort(v_blendMode);
#endif // !@DRAW_IMAGE_MESH
#endif
half4 dstColorPremul = DST_COLOR_FETCH(@dstColorTexture);
color.rgb = advanced_color_blend(color.rgb, dstColorPremul, blendMode);
#endif // @FIXED_FUNCTION_COLOR_OUTPUT
// Src-over blending is enabled, so just premultiply and let the HW
// finish the the the alpha portion of the blend mode.
color.rgb *= color.a;
}
#endif // @ENABLE_ADVANCED_BLEND && !@FIXED_FUNCTION_COLOR_OUTPUT
#endif // @ENABLE_ADVANCED_BLEND
// Certain platforms give us less control of the format of what we are
// rendering too. Specifically, we are auto converted from linear -> sRGB on

View File

@@ -81,14 +81,14 @@
#endif
// clang-format off
#if defined(@RENDER_MODE_MSAA) && defined(@ENABLE_CLIP_RECT) && defined(GL_ES)
// clang-format on
#if defined(@RENDER_MODE_MSAA) && defined(@ENABLE_CLIP_RECT) && defined(GL_ES) && !defined(@DISABLE_CLIP_DISTANCE_FOR_UBERSHADERS)
#ifdef GL_EXT_clip_cull_distance
#extension GL_EXT_clip_cull_distance : require
#elif defined(GL_ANGLE_clip_cull_distance)
#extension GL_ANGLE_clip_cull_distance : require
#endif
#endif // RENDER_MODE_MSAA && ENABLE_CLIP_RECT
#endif // RENDER_MODE_MSAA && ENABLE_CLIP_RECT && GL_ES && !DISABLE_CLIP_DISTANCE_FOR_UBERSHADERS
// clang-format on
#if @GLSL_VERSION >= 310
#define UNIFORM_BLOCK_BEGIN(IDX, NAME) \
@@ -169,10 +169,6 @@
#define TEXTURE_R32UI(SET, IDX, NAME) \
layout(binding = IDX) uniform highp utexture2D NAME
#if defined(@FRAGMENT) && defined(@RENDER_MODE_MSAA)
#define DST_COLOR_TEXTURE(NAME) \
layout(input_attachment_index = 0, \
binding = COLOR_PLANE_IDX, \
set = PLS_TEXTURE_BINDINGS_SET) uniform lowp subpassInputMS NAME
#endif // @FRAGMENT && @RENDER_MODE_MSAA
#elif @GLSL_VERSION >= 310
#define TEXTURE_RGBA32UI(SET, IDX, NAME) \
@@ -187,8 +183,6 @@
layout(binding = IDX) uniform highp isampler2D NAME
#define TEXTURE_R32UI(SET, IDX, NAME) \
layout(binding = IDX) uniform highp usampler2D NAME
#define DST_COLOR_TEXTURE(NAME) \
TEXTURE_RGBA8(PER_FLUSH_BINDINGS_SET, DST_COLOR_TEXTURE_IDX, NAME)
#else
#define TEXTURE_RGBA32UI(SET, IDX, NAME) uniform highp usampler2D NAME
#define TEXTURE_RGBA32F(SET, IDX, NAME) uniform highp sampler2D NAME
@@ -196,8 +190,6 @@
#define TEXTURE_R16F(SET, IDX, NAME) uniform mediump sampler2D NAME
#define TEXTURE_R32I(SET, IDX, NAME) uniform highp isampler2D NAME
#define TEXTURE_R32UI(SET, IDX, NAME) uniform highp usampler2D NAME
#define DST_COLOR_TEXTURE(NAME) \
TEXTURE_RGBA8(PER_FLUSH_BINDINGS_SET, DST_COLOR_TEXTURE_IDX, NAME)
#endif
#ifdef @TARGET_VULKAN
@@ -219,12 +211,6 @@
textureGrad(sampler2D(NAME, SAMPLER_NAME), COORD, DDX, DDY)
#if defined(@FRAGMENT) && defined(@RENDER_MODE_MSAA)
#extension GL_OES_sample_variables : require
#define DST_COLOR_FETCH(NAME) \
dst_color_fetch(mat4(subpassLoad(NAME, 0), \
subpassLoad(NAME, 1), \
subpassLoad(NAME, 2), \
subpassLoad(NAME, 3)), \
gl_SampleMaskIn[0])
#endif // @FRAGMENT && @RENDER_MODE_MSAA
#else // @TARGET_VULKAN -> !@TARGET_VULKAN
// SAMPLER_LINEAR and SAMPLER_MIPMAP are no-ops because in GL, sampling
@@ -239,7 +225,6 @@
texture(NAME, COORD, LODBIAS)
#define TEXTURE_SAMPLE_GRAD(NAME, SAMPLER_NAME, COORD, DDX, DDY) \
textureGrad(NAME, COORD, DDX, DDY)
#define DST_COLOR_FETCH(NAME) texelFetch(NAME, ivec2(floor(_fragCoord.xy)), 0)
#endif // !@TARGET_VULKAN
#define TEXTURE_SAMPLE_DYNAMIC(TEXTURE, SAMPLER_NAME, COORD) \
@@ -644,6 +629,23 @@
#define EMIT_PLS_AND_FRAG_COLOR EMIT_PLS
#if defined(@TARGET_VULKAN) && !defined(@INPUT_ATTACHMENT_NONE)
#define DST_COLOR_TEXTURE(NAME) \
layout(input_attachment_index = 0, \
binding = COLOR_PLANE_IDX, \
set = PLS_TEXTURE_BINDINGS_SET) uniform lowp subpassInputMS NAME
#define DST_COLOR_FETCH(NAME) \
dst_color_fetch(mat4(subpassLoad(NAME, 0), \
subpassLoad(NAME, 1), \
subpassLoad(NAME, 2), \
subpassLoad(NAME, 3)), \
gl_SampleMaskIn[0])
#else
#define DST_COLOR_TEXTURE(NAME) \
TEXTURE_RGBA8(PER_FLUSH_BINDINGS_SET, DST_COLOR_TEXTURE_IDX, NAME)
#define DST_COLOR_FETCH(NAME) texelFetch(NAME, ivec2(floor(_fragCoord.xy)), 0)
#endif
#define MUL(A, B) ((A) * (B))
precision highp float;

View File

@@ -22,17 +22,7 @@ layout(constant_id = VULKAN_VENDOR_ID_SPECIALIZATION_IDX) const uint
kVulkanVendorID = 0;
#define @ENABLE_CLIPPING kEnableClipping
// MSAA uses gl_ClipDistance when ENABLE_CLIP_RECT is set, but since Vulkan is
// using specialization constants (as opposed to compile-time flags), it means
// that the usage of them is in the compiled shader even if that codepath is
// not going to be taken, which ends up as a validation failure on systems that
// do not support that extension. In those cases, we can just not define
// ENABLE_CLIP_RECT to avoid all of the gl_ClipDistance usages.
#ifndef @DISABLE_CLIP_RECT_FOR_VULKAN_MSAA
#define @ENABLE_CLIP_RECT kEnableClipRect
#endif
#define @ENABLE_ADVANCED_BLEND kEnableAdvancedBlend
#define @ENABLE_FEATHER kEnableFeather
#define @ENABLE_EVEN_ODD kEnableEvenOdd

View File

@@ -647,10 +647,12 @@ RenderContextVulkanImpl::RenderContextVulkanImpl(
break;
case VULKAN_VENDOR_ARM:
// This is undocumented, but raster ordering always works on ARM
// Mali GPUs if you define a subpass dependency, even without
case VULKAN_VENDOR_IMG_TEC:
// This is undocumented, but raster ordering works on Mali and
// PowerVR if you define a subpass dependency, even without
// EXT_rasterization_order_attachment_access.
m_platformFeatures.supportsRasterOrderingMode = true;
m_platformFeatures.supportsRasterOrderingMode =
!contextOptions.forceAtomicMode;
break;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ namespace rive::gpu
{
class RenderTarget;
class RenderContextGLImpl;
class RenderContextWebGPUImpl;
class VulkanContext;
}; // namespace rive::gpu
@@ -40,5 +41,10 @@ public:
uint32_t width,
uint32_t height,
bool riveRenderable);
static rive::rcp<OffscreenRenderTarget> MakeWebGPU(
rive::gpu::RenderContextWebGPUImpl*,
uint32_t width,
uint32_t height);
};
}; // namespace rive_tests

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2025 Rive
*/
#include "offscreen_render_target.hpp"
#if !defined(RIVE_WEBGPU) && !defined(RIVE_DAWN)
namespace rive_tests
{
rive::rcp<OffscreenRenderTarget> OffscreenRenderTarget::MakeWebGPU(
rive::gpu::RenderContextWebGPUImpl*,
uint32_t width,
uint32_t height)
{
return nullptr;
}
} // namespace rive_tests
#else
#include "rive/renderer/rive_render_image.hpp"
#include "rive/renderer/webgpu/render_context_webgpu_impl.hpp"
#ifdef RIVE_WAGYU
#include <webgpu/webgpu_wagyu.h>
#endif
namespace rive_tests
{
class OffscreenRenderTargetWebGPU : public rive_tests::OffscreenRenderTarget
{
public:
OffscreenRenderTargetWebGPU(rive::gpu::RenderContextWebGPUImpl* impl,
wgpu::TextureFormat format,
uint32_t width,
uint32_t height) :
m_textureTarget(
rive::make_rcp<TextureTarget>(impl, format, width, height))
{}
rive::RenderImage* asRenderImage() override
{
return m_textureTarget->renderImage();
}
rive::gpu::RenderTarget* asRenderTarget() override
{
return m_textureTarget.get();
}
private:
class TextureTarget : public rive::gpu::RenderTargetWebGPU
{
public:
TextureTarget(rive::gpu::RenderContextWebGPUImpl* impl,
wgpu::TextureFormat format,
uint32_t width,
uint32_t height) :
RenderTargetWebGPU(impl->device(),
impl->capabilities(),
format,
width,
height)
{
wgpu::TextureDescriptor textureDesc = {
.usage = wgpu::TextureUsage::TextureBinding |
wgpu::TextureUsage::RenderAttachment |
wgpu::TextureUsage::CopySrc,
.dimension = wgpu::TextureDimension::e2D,
.size = {width, height},
.format = format,
};
#ifdef RIVE_WAGYU
if (impl->capabilities().plsType ==
rive::gpu::RenderContextWebGPUImpl::PixelLocalStorageType::
VK_EXT_rasterization_order_attachment_access)
{
textureDesc.usage |= static_cast<wgpu::TextureUsage>(
WGPUTextureUsage_WagyuInputAttachment);
}
#endif
auto texture = rive::make_rcp<rive::gpu::TextureWebGPUImpl>(
width,
height,
impl->device().CreateTexture(&textureDesc));
setTargetTextureView(texture->textureView(), texture->texture());
m_renderImage =
rive::make_rcp<rive::RiveRenderImage>(std::move(texture));
}
rive::RiveRenderImage* renderImage() const
{
return m_renderImage.get();
}
private:
rive::rcp<rive::RiveRenderImage> m_renderImage;
};
rive::rcp<TextureTarget> m_textureTarget;
};
rive::rcp<OffscreenRenderTarget> OffscreenRenderTarget::MakeWebGPU(
rive::gpu::RenderContextWebGPUImpl* impl,
uint32_t width,
uint32_t height)
{
return rive::make_rcp<OffscreenRenderTargetWebGPU>(
impl,
wgpu::TextureFormat::RGBA8Unorm,
width,
height);
}
}; // namespace rive_tests
#endif

View File

@@ -238,6 +238,11 @@ TestingWindow::Backend TestingWindow::ParseBackend(const char* name,
params->clockwise = true;
return Backend::wgpu;
}
if (nameStr == "wgpumsaa")
{
params->msaa = true;
return Backend::wgpu;
}
if (nameStr == "rhi")
{
return Backend::rhi;

View File

@@ -381,6 +381,16 @@ public:
height,
riveRenderable);
}
#endif
#if defined(RIVE_WEBGPU) || defined(RIVE_DAWN)
if (auto* renderContextWebGPU =
m_fiddleContext->renderContextWebGPUImpl())
{
return rive_tests::OffscreenRenderTarget::MakeWebGPU(
renderContextWebGPU,
width,
height);
}
#endif
return nullptr;
}

View File

@@ -71,82 +71,6 @@ static const char* pls_impl_name(
return "<no pixel local storage>";
}
class OffscreenRenderTargetWGPU : public rive_tests::OffscreenRenderTarget
{
public:
OffscreenRenderTargetWGPU(
rive::gpu::RenderContextWebGPUImpl* renderContextImpl,
wgpu::TextureFormat format,
uint32_t width,
uint32_t height) :
m_textureTarget(rive::make_rcp<TextureTarget>(renderContextImpl,
format,
width,
height))
{}
rive::RenderImage* asRenderImage() override
{
return m_textureTarget->renderImage();
}
rive::gpu::RenderTarget* asRenderTarget() override
{
return m_textureTarget.get();
}
private:
class TextureTarget : public RenderTargetWebGPU
{
public:
TextureTarget(rive::gpu::RenderContextWebGPUImpl* renderContextImpl,
wgpu::TextureFormat format,
uint32_t width,
uint32_t height) :
RenderTargetWebGPU(renderContextImpl->device(),
renderContextImpl->capabilities(),
format,
width,
height)
{
wgpu::TextureDescriptor textureDesc = {
.usage = wgpu::TextureUsage::TextureBinding |
wgpu::TextureUsage::RenderAttachment,
.dimension = wgpu::TextureDimension::e2D,
.size = {width, height},
.format = format,
};
#ifdef RIVE_WAGYU
if (renderContextImpl->capabilities().plsType ==
RenderContextWebGPUImpl::PixelLocalStorageType::
VK_EXT_rasterization_order_attachment_access)
{
textureDesc.usage |= static_cast<wgpu::TextureUsage>(
WGPUTextureUsage_WagyuInputAttachment);
}
#endif
auto texture = make_rcp<TextureWebGPUImpl>(
width,
height,
renderContextImpl->device().CreateTexture(&textureDesc));
setTargetTextureView(texture->textureView(), texture->texture());
m_renderImage =
rive::make_rcp<rive::RiveRenderImage>(std::move(texture));
}
rive::RiveRenderImage* renderImage() const
{
return m_renderImage.get();
}
private:
rive::rcp<rive::RiveRenderImage> m_renderImage;
};
rive::rcp<TextureTarget> m_textureTarget;
};
class TestingWindowWGPU : public TestingWindow
{
public:
@@ -257,20 +181,9 @@ public:
uint32_t height,
bool riveRenderable) const override
{
return make_rcp<OffscreenRenderTargetWGPU>(
impl(),
// The format has no impact on whether Rive can render directly to
// the texture, but switch on that flag to test both formats.
//
// NOTE: The WebGPU backend currently has no code to handle
// non-renderable textures. GL_EXT_shader_pixel_local_storage has no
// such restrictions and
// VK_EXT_rasterization_order_attachment_access mode requires the
// texture to support input attachments.
riveRenderable ? wgpu::TextureFormat::RGBA8Unorm
: wgpu::TextureFormat::BGRA8Unorm,
width,
height);
return rive_tests::OffscreenRenderTarget::MakeWebGPU(impl(),
width,
height);
}
void resize(int width, int height) override