mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 21:21:17 +01:00
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:
@@ -1 +1 @@
|
||||
81f6b8ffa7278517623faeafeaa93569dcbc3640
|
||||
69b2a3c6431ea5cd460654c2c1fb997a9a55fc7f
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*,
|
||||
|
||||
@@ -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 = {};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
118
tests/common/offscreen_render_target_webgpu.cpp
Normal file
118
tests/common/offscreen_render_target_webgpu.cpp
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user