diff --git a/lib/aurora.cpp b/lib/aurora.cpp index 48e295a..884d6c1 100644 --- a/lib/aurora.cpp +++ b/lib/aurora.cpp @@ -239,13 +239,16 @@ void end_frame() noexcept { presentSource.size.width, presentSource.size.height); wgpu::BindGroup rmlBindGroup; + bool rmlOverlay = false; #if AURORA_ENABLE_RMLUI if (rmlui::is_initialized()) { - rmlBindGroup = rmlui::record_frame(viewport); + auto rmlFrame = rmlui::record_frame(viewport); + rmlBindGroup = std::move(rmlFrame.bindGroup); + rmlOverlay = rmlFrame.overlay; } #endif - gfx::end_frame([rmlBindGroup = std::move(rmlBindGroup), viewport, + gfx::end_frame([rmlBindGroup = std::move(rmlBindGroup), rmlOverlay, viewport, imguiDrawData = std::move(imguiDrawData)](wgpu::CommandEncoder& encoder) { wgpu::Texture currentTexture; wgpu::TextureView currentView; @@ -267,7 +270,7 @@ void end_frame() noexcept { const bool canPresent = currentTexture && currentView; if (canPresent) { wgpu::BindGroup presentBindGroup; - if (rmlBindGroup) { + if (rmlBindGroup && !rmlOverlay) { presentBindGroup = rmlBindGroup; } else { const auto& resampledSource = webgpu::resample_present_source(encoder, viewport); @@ -294,6 +297,11 @@ void end_frame() noexcept { pass.SetViewport(viewport.left, viewport.top, viewport.width, viewport.height, viewport.znear, viewport.zfar); pass.Draw(3); + if (rmlBindGroup && rmlOverlay) { + pass.SetPipeline(webgpu::g_CopyPremultipliedAlphaPipeline); + pass.SetBindGroup(0, rmlBindGroup, 0, nullptr); + pass.Draw(3); + } pass.End(); } { diff --git a/lib/rmlui.cpp b/lib/rmlui.cpp index f553daa..50dbb71 100644 --- a/lib/rmlui.cpp +++ b/lib/rmlui.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -82,6 +83,32 @@ void ensure_render_target(Rml::Vector2i dimensions) noexcept { s_renderTargetCopyBindGroup = webgpu::create_copy_bind_group(s_renderTarget); } +bool element_has_visible_backdrop_filter(const Rml::Element* element) noexcept { + if (element == nullptr || !element->IsVisible(true)) { + return false; + } + + const auto& computed = element->GetComputedValues(); + if (computed.opacity() > 0.f && computed.has_backdrop_filter()) { + return true; + } + + const int childCount = element->GetNumChildren(); + for (int childIndex = 0; childIndex < childCount; ++childIndex) { + if (element_has_visible_backdrop_filter(element->GetChild(childIndex))) { + return true; + } + } + return false; +} + +bool context_has_visible_backdrop_filter(Rml::Context* context) noexcept { + if (context == nullptr) { + return false; + } + return element_has_visible_backdrop_filter(context->GetRootElement()); +} + struct MappedPoint { Rml::Vector2f position; bool valid = false; @@ -408,7 +435,7 @@ void handle_event(SDL_Event& event) noexcept { RmlSDL::InputEventHandler(g_context, window::get_sdl_window(), event); } -wgpu::BindGroup record_frame(const webgpu::Viewport& presentViewport) noexcept { +RecordedFrame record_frame(const webgpu::Viewport& presentViewport) noexcept { if (g_context == nullptr) { return {}; } @@ -422,10 +449,12 @@ wgpu::BindGroup record_frame(const webgpu::Viewport& presentViewport) noexcept { sync_context_metrics(dim); g_context->Update(); + const bool needsBackdrop = context_has_visible_backdrop_filter(g_context); auto* renderInterface = get_render_interface(); renderInterface->SetWindowSize(g_context->GetDimensions()); - renderInterface->BeginFrame(s_renderTarget, webgpu::present_source()); + renderInterface->BeginFrame(s_renderTarget, webgpu::present_source(), + needsBackdrop ? BaseLayerContent::Scene : BaseLayerContent::Transparent); Backend::BeginFrame(); g_context->Render(); @@ -435,7 +464,10 @@ wgpu::BindGroup record_frame(const webgpu::Viewport& presentViewport) noexcept { // We didn't render anything return {}; } - return s_renderTargetCopyBindGroup; + return { + .bindGroup = s_renderTargetCopyBindGroup, + .overlay = !needsBackdrop, + }; } void shutdown() noexcept { diff --git a/lib/rmlui.hpp b/lib/rmlui.hpp index 2bd4eb7..787bb2d 100644 --- a/lib/rmlui.hpp +++ b/lib/rmlui.hpp @@ -9,9 +9,14 @@ namespace aurora::rmlui { +struct RecordedFrame { + wgpu::BindGroup bindGroup; + bool overlay = false; +}; + void initialize(const AuroraWindowSize& size) noexcept; void handle_event(SDL_Event& event) noexcept; -wgpu::BindGroup record_frame(const webgpu::Viewport& presentViewport) noexcept; +RecordedFrame record_frame(const webgpu::Viewport& presentViewport) noexcept; void shutdown() noexcept; } // namespace aurora::rmlui diff --git a/lib/rmlui/WebGPURenderInterface.cpp b/lib/rmlui/WebGPURenderInterface.cpp index b8fd576..acac5fe 100644 --- a/lib/rmlui/WebGPURenderInterface.cpp +++ b/lib/rmlui/WebGPURenderInterface.cpp @@ -27,6 +27,7 @@ namespace { Module Log("aurora::rmlui::RenderInterface"); constexpr size_t rmlBufferOffsetAlignment = 4; +constexpr float FilterEpsilon = 0.0001f; struct Image { std::unique_ptr data; @@ -53,6 +54,23 @@ struct CompiledShaderData { GradientUniformBlock gradient; }; +enum class FilterType { + Opacity, + Blur, + DropShadow, + ColorMatrix, + MaskImage, +}; + +struct CompiledFilter { + FilterType type = FilterType::Blur; + float opacity = 1.f; + float sigma = 0.f; + Rml::Vector2f offset; + Rml::ColourbPremultiplied color; + Rml::Matrix4f colorMatrix; +}; + Image get_image(const Rml::String& source) { FileInterface_SDL fileInterface; const Rml::FileHandle file = fileInterface.Open(source); @@ -158,6 +176,74 @@ Rml::ColumnMajorMatrix4f to_shader_matrix(const Rml::Matrix4f& matrix) { } } +bool is_identity_matrix(const Rml::Matrix4f& matrix) { + const Rml::Matrix4f identity = Rml::Matrix4f::Identity(); + const auto* matrixData = matrix.data(); + const auto* identityData = identity.data(); + for (size_t i = 0; i < 16; ++i) { + if (std::abs(matrixData[i] - identityData[i]) > FilterEpsilon) { + return false; + } + } + return true; +} + +bool is_identity_filter(const CompiledFilter& filter) { + switch (filter.type) { + case FilterType::Opacity: + return std::abs(filter.opacity - 1.f) <= FilterEpsilon; + case FilterType::Blur: + return filter.sigma < 0.5f; + case FilterType::ColorMatrix: + return is_identity_matrix(filter.colorMatrix); + case FilterType::DropShadow: + case FilterType::MaskImage: + default: + return false; + } +} + +std::vector active_filters(Rml::Span filters) { + std::vector activeFilters; + activeFilters.reserve(filters.size()); + for (Rml::CompiledFilterHandle filterHandle : filters) { + const auto* filter = reinterpret_cast(filterHandle); + if (filter != nullptr && !is_identity_filter(*filter)) { + activeFilters.push_back(filterHandle); + } + } + return activeFilters; +} + +bool try_fold_simple_filters(Rml::Span filters, Rml::Matrix4f& colorMatrix, + float& opacity) { + colorMatrix = Rml::Matrix4f::Identity(); + opacity = 1.f; + + for (Rml::CompiledFilterHandle filterHandle : filters) { + const auto* filter = reinterpret_cast(filterHandle); + if (filter == nullptr) { + continue; + } + + switch (filter->type) { + case FilterType::Opacity: + opacity *= filter->opacity; + break; + case FilterType::ColorMatrix: + colorMatrix = filter->colorMatrix * colorMatrix; + break; + case FilterType::Blur: + case FilterType::DropShadow: + case FilterType::MaskImage: + default: + return false; + } + } + + return true; +} + Rml::Rectanglei downsample_scissor(Rml::Rectanglei scissor) { scissor.p0 = (scissor.p0 + Rml::Vector2i(1)) / 2; scissor.p1 = Rml::Math::Max(scissor.p1 / 2, scissor.p0); @@ -223,6 +309,18 @@ gfx::PipelineRef blit_pipeline(wgpu::TextureFormat colorFormat, uint32_t sampleC blend ? BlendMode::Premultiplied : BlendMode::None)); } +gfx::PipelineRef simple_filter_pipeline(wgpu::TextureFormat colorFormat, uint32_t sampleCount, + WebGPURenderInterface::BlitPipelineType type, bool useStencil) { + const bool blend = type == WebGPURenderInterface::BlitPipelineType::Blend || + type == WebGPURenderInterface::BlitPipelineType::BlendMasked; + const bool masked = type == WebGPURenderInterface::BlitPipelineType::BlendMasked || + type == WebGPURenderInterface::BlitPipelineType::ReplaceMasked; + return gfx::pipeline_ref( + make_pipeline_config(PipelineKind::SimpleFilter, colorFormat, sampleCount, VertexLayoutKind::Fullscreen, + masked ? StencilMode::EqualKeep : (useStencil ? StencilMode::AlwaysKeep : StencilMode::None), + blend ? BlendMode::Premultiplied : BlendMode::None)); +} + gfx::PipelineRef seed_resample_pipeline(wgpu::TextureFormat colorFormat, uint32_t sampleCount, bool useStencil) { return gfx::pipeline_ref( make_pipeline_config(PipelineKind::SeedResample, colorFormat, sampleCount, VertexLayoutKind::Fullscreen, @@ -640,6 +738,11 @@ void WebGPURenderInterface::EnsureFrameRenderingStarted() { } m_frameRenderingStarted = true; + if (m_baseLayerContent == BaseLayerContent::Transparent) { + BeginLayerPass(0, wgpu::LoadOp::Clear, "RmlUi transparent base layer pass", true); + return; + } + const auto seedBindGroup = texture_bind_group_ref(m_frameSeedView); const auto seedUniformRange = gfx::push_uniform(SeedResampleUniformBlock{ .samplerMode = sampler_mode(), @@ -832,9 +935,8 @@ void WebGPURenderInterface::RenderBlur(float sigma, const RenderTarget& sourceDe } } -void WebGPURenderInterface::RenderFilters(Rml::Span filters) { - constexpr size_t sourceIndex = 0; - constexpr size_t shadowIndex = 1; +size_t WebGPURenderInterface::RenderFilters(Rml::Span filters) { + size_t sourceIndex = 0; constexpr size_t tempIndex = 2; for (Rml::CompiledFilterHandle filterHandle : filters) { @@ -842,22 +944,24 @@ void WebGPURenderInterface::RenderFilters(Rml::Spantype) { case FilterType::Opacity: { - BeginRenderTargetPass(m_postprocessTargets[shadowIndex].view, wgpu::LoadOp::Clear, "RmlUi opacity pass"); + const SimpleFilterUniformBlock simpleFilterUniform{ + .matrix = to_shader_matrix(Rml::Matrix4f::Identity()), + .opacity = {filter->opacity, 0.f, 0.f, 0.f}, + }; + const auto simpleFilterRange = gfx::push_uniform(simpleFilterUniform); + BeginRenderTargetPass(m_postprocessTargets[scratchIndex].view, wgpu::LoadOp::Clear, "RmlUi opacity pass"); DrawFullscreenTexture(texture_bind_group_ref(m_postprocessTargets[sourceIndex].view), - filter_pipeline(PipelineKind::Opacity, m_renderTargetFormat, VertexLayoutKind::Fullscreen, - BlendMode::Opacity), - 0, {}, true, {filter->opacity, filter->opacity, filter->opacity, filter->opacity}, true); - CompositeToTarget(texture_bind_group_ref(m_postprocessTargets[shadowIndex].view), - m_postprocessTargets[sourceIndex].view, wgpu::LoadOp::Clear, - blit_pipeline(m_renderTargetFormat, 1, BlitPipelineType::Replace, false), - "RmlUi opacity result copy pass"); + filter_pipeline(PipelineKind::SimpleFilter, m_renderTargetFormat), uniform_bind_group_ref(), + simpleFilterRange); + sourceIndex = scratchIndex; break; } case FilterType::Blur: - RenderBlur(filter->sigma, m_postprocessTargets[sourceIndex], m_postprocessTargets[shadowIndex]); + RenderBlur(filter->sigma, m_postprocessTargets[sourceIndex], m_postprocessTargets[tempIndex]); break; case FilterType::DropShadow: { TexCoordLimits texCoordLimits = GetPostprocessTexCoordLimits(); @@ -883,58 +987,52 @@ void WebGPURenderInterface::RenderFilters(Rml::Spansigma, m_postprocessTargets[shadowIndex], m_postprocessTargets[tempIndex]); + RenderBlur(filter->sigma, m_postprocessTargets[scratchIndex], m_postprocessTargets[tempIndex]); CompositeToTarget(texture_bind_group_ref(m_postprocessTargets[sourceIndex].view), - m_postprocessTargets[shadowIndex].view, wgpu::LoadOp::Load, + m_postprocessTargets[scratchIndex].view, wgpu::LoadOp::Load, blit_pipeline(m_renderTargetFormat, 1, BlitPipelineType::Blend, false), "RmlUi drop shadow source composite pass"); - CompositeToTarget(texture_bind_group_ref(m_postprocessTargets[shadowIndex].view), - m_postprocessTargets[sourceIndex].view, wgpu::LoadOp::Clear, - blit_pipeline(m_renderTargetFormat, 1, BlitPipelineType::Replace, false), - "RmlUi drop shadow result copy pass"); + sourceIndex = scratchIndex; break; } case FilterType::ColorMatrix: { - const ColorMatrixUniformBlock colorMatrixUniform{ + const SimpleFilterUniformBlock simpleFilterUniform{ .matrix = to_shader_matrix(filter->colorMatrix), + .opacity = {1.f, 0.f, 0.f, 0.f}, }; - const auto colorMatrixRange = gfx::push_uniform(colorMatrixUniform); + const auto simpleFilterRange = gfx::push_uniform(simpleFilterUniform); CompositeToTarget(texture_bind_group_ref(m_postprocessTargets[sourceIndex].view), - m_postprocessTargets[shadowIndex].view, wgpu::LoadOp::Clear, - filter_pipeline(PipelineKind::ColorMatrix, m_renderTargetFormat), "RmlUi color matrix pass", - uniform_bind_group_ref(), colorMatrixRange); - CompositeToTarget(texture_bind_group_ref(m_postprocessTargets[shadowIndex].view), - m_postprocessTargets[sourceIndex].view, wgpu::LoadOp::Clear, - blit_pipeline(m_renderTargetFormat, 1, BlitPipelineType::Replace, false), - "RmlUi color matrix result copy pass"); + m_postprocessTargets[scratchIndex].view, wgpu::LoadOp::Clear, + filter_pipeline(PipelineKind::SimpleFilter, m_renderTargetFormat), "RmlUi color matrix pass", + uniform_bind_group_ref(), simpleFilterRange); + sourceIndex = scratchIndex; break; } case FilterType::MaskImage: { CompositeToTarget(texture_bind_group_ref(m_postprocessTargets[sourceIndex].view), - m_postprocessTargets[shadowIndex].view, wgpu::LoadOp::Clear, + m_postprocessTargets[scratchIndex].view, wgpu::LoadOp::Clear, filter_pipeline(PipelineKind::MaskImage, m_renderTargetFormat), "RmlUi mask image pass", texture_bind_group_ref(m_blendMaskTarget.view), {}, false); - CompositeToTarget(texture_bind_group_ref(m_postprocessTargets[shadowIndex].view), - m_postprocessTargets[sourceIndex].view, wgpu::LoadOp::Clear, - blit_pipeline(m_renderTargetFormat, 1, BlitPipelineType::Replace, false), - "RmlUi mask image result copy pass"); + sourceIndex = scratchIndex; break; } } } EndActivePass(); + return sourceIndex; } void WebGPURenderInterface::BeginFrame(const webgpu::TextureWithSampler& target, - const webgpu::TextureWithSampler& seedTarget) { - m_frameSeedView = seedTarget.view; + const webgpu::TextureWithSampler& sceneTarget, + BaseLayerContent baseLayerContent) { + m_frameSeedView = sceneTarget.view; m_frameSize = target.size; m_viewport = { .left = 0.f, @@ -950,6 +1048,7 @@ void WebGPURenderInterface::BeginFrame(const webgpu::TextureWithSampler& target, m_frameRenderingStarted = false; m_frameActive = true; m_passActive = false; + m_baseLayerContent = baseLayerContent; NewFrame(); EnsureFrameTargets(target.size); @@ -998,6 +1097,7 @@ bool WebGPURenderInterface::EndFrame() { m_frameActive = false; m_frameSeedView = {}; m_frameRenderingStarted = false; + m_baseLayerContent = BaseLayerContent::Transparent; return rendered; } @@ -1024,17 +1124,52 @@ void WebGPURenderInterface::CompositeLayers(Rml::LayerHandle source, Rml::LayerH } EnsureFrameRenderingStarted(); - CompositeToTarget(texture_bind_group_ref(m_layers[source].view), m_postprocessTargets[0].view, wgpu::LoadOp::Clear, - blit_pipeline(m_renderTargetFormat, 1, BlitPipelineType::Replace, false), "RmlUi layer copy pass"); const Rml::LayerHandle topLayer = m_layerStack.empty() ? 0 : m_layerStack.back(); - RenderFilters(filters); + const std::vector activeFilterHandles = active_filters(filters); + const Rml::Span activeFilters{activeFilterHandles.data(), activeFilterHandles.size()}; const bool replace = blendMode == Rml::BlendMode::Replace; const BlitPipelineType pipelineType = replace ? (m_clipMaskEnabled ? BlitPipelineType::ReplaceMasked : BlitPipelineType::Replace) : (m_clipMaskEnabled ? BlitPipelineType::BlendMasked : BlitPipelineType::Blend); + if (activeFilters.empty() && source != destination) { + BeginLayerPass(destination, wgpu::LoadOp::Load, "RmlUi layer direct composite pass"); + DrawFullscreenTexture(texture_bind_group_ref(m_layers[source].view), + blit_pipeline(m_renderTargetFormat, LayerSampleCount, pipelineType, true)); + + if (destination != topLayer) { + EndActivePass(); + m_activeLayer = topLayer; + } + return; + } + + Rml::Matrix4f simpleFilterMatrix; + float simpleFilterOpacity = 1.f; + if (source != destination && try_fold_simple_filters(activeFilters, simpleFilterMatrix, simpleFilterOpacity)) { + const SimpleFilterUniformBlock simpleFilterUniform{ + .matrix = to_shader_matrix(simpleFilterMatrix), + .opacity = {simpleFilterOpacity, 0.f, 0.f, 0.f}, + }; + const auto simpleFilterRange = gfx::push_uniform(simpleFilterUniform); + BeginLayerPass(destination, wgpu::LoadOp::Load, "RmlUi simple filter composite pass"); + DrawFullscreenTexture(texture_bind_group_ref(m_layers[source].view), + simple_filter_pipeline(m_renderTargetFormat, LayerSampleCount, pipelineType, true), + uniform_bind_group_ref(), simpleFilterRange); + + if (destination != topLayer) { + EndActivePass(); + m_activeLayer = topLayer; + } + return; + } + + CompositeToTarget(texture_bind_group_ref(m_layers[source].view), m_postprocessTargets[0].view, wgpu::LoadOp::Clear, + blit_pipeline(m_renderTargetFormat, 1, BlitPipelineType::Replace, false), "RmlUi layer copy pass"); + const size_t filteredIndex = RenderFilters(activeFilters); + BeginLayerPass(destination, wgpu::LoadOp::Load, "RmlUi layer composite pass"); - DrawFullscreenTexture(texture_bind_group_ref(m_postprocessTargets[0].view), + DrawFullscreenTexture(texture_bind_group_ref(m_postprocessTargets[filteredIndex].view), blit_pipeline(m_renderTargetFormat, LayerSampleCount, pipelineType, true)); if (destination != topLayer) { diff --git a/lib/rmlui/WebGPURenderInterface.hpp b/lib/rmlui/WebGPURenderInterface.hpp index 9e9d4eb..1cce858 100644 --- a/lib/rmlui/WebGPURenderInterface.hpp +++ b/lib/rmlui/WebGPURenderInterface.hpp @@ -41,8 +41,9 @@ struct DropShadowUniformBlock { Rml::Vector2f texCoordMax; }; -struct ColorMatrixUniformBlock { +struct SimpleFilterUniformBlock { Rml::ColumnMajorMatrix4f matrix; + Rml::Vector4f opacity; }; struct GradientUniformBlock { @@ -60,6 +61,11 @@ struct TexCoordLimits { Rml::Vector2f max; }; +enum class BaseLayerContent { + Transparent, + Scene, +}; + class WebGPURenderInterface : public Rml::RenderInterface { public: static constexpr wgpu::TextureFormat ClipMaskStencilFormat = wgpu::TextureFormat::Stencil8; @@ -81,14 +87,6 @@ public: }; private: - enum class FilterType { - Opacity, - Blur, - DropShadow, - ColorMatrix, - MaskImage, - }; - struct RenderTarget { wgpu::Texture texture; wgpu::TextureView view; @@ -97,15 +95,6 @@ private: wgpu::Extent3D size; }; - struct CompiledFilter { - FilterType type = FilterType::Blur; - float opacity = 1.f; - float sigma = 0.f; - Rml::Vector2f offset; - Rml::ColourbPremultiplied color; - Rml::Matrix4f colorMatrix; - }; - wgpu::TextureView m_frameSeedView; wgpu::TextureFormat m_renderTargetFormat = wgpu::TextureFormat::Undefined; @@ -120,10 +109,11 @@ private: Rml::Vector2i m_clipResetGeometrySize{}; std::vector m_layers; std::array m_postprocessTargets{}; - RenderTarget m_blendMaskTarget; + RenderTarget m_blendMaskTarget{}; std::vector m_layerStack; Rml::LayerHandle m_activeLayer = 0; Rml::LayerHandle m_nextLayer = 1; + BaseLayerContent m_baseLayerContent = BaseLayerContent::Transparent; Rml::Vector2i m_windowSize{}; Rml::Matrix4f m_translationMatrix = Rml::Matrix4f::Identity(); @@ -169,7 +159,7 @@ private: gfx::Range extraUniformRange = {}, bool extraBindGroupHasDynamicOffset = true, std::array blendConstant = {}, bool hasBlendConstant = false); void RenderBlur(float sigma, const RenderTarget& sourceDestination, const RenderTarget& temp); - void RenderFilters(Rml::Span filters); + size_t RenderFilters(Rml::Span filters); public: Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, @@ -199,7 +189,8 @@ public: Rml::TextureHandle texture) override; void ReleaseShader(Rml::CompiledShaderHandle shader) override; - void BeginFrame(const webgpu::TextureWithSampler& target, const webgpu::TextureWithSampler& seed_target); + void BeginFrame(const webgpu::TextureWithSampler& target, const webgpu::TextureWithSampler& scene_target, + BaseLayerContent baseLayerContent); bool EndFrame(); void SetWindowSize(const Rml::Vector2i& window_size) { m_windowSize = window_size; } void SetRenderTargetFormat(wgpu::TextureFormat render_target_format) { m_renderTargetFormat = render_target_format; } diff --git a/lib/rmlui/pipeline.cpp b/lib/rmlui/pipeline.cpp index e6dfab7..a9f6dc8 100644 --- a/lib/rmlui/pipeline.cpp +++ b/lib/rmlui/pipeline.cpp @@ -24,8 +24,8 @@ constexpr uint32_t DynamicGroup2 = 1u << 2u; constexpr uint64_t CommonUniformBindingSize = AURORA_ALIGN(sizeof(UniformBlock), 16); constexpr uint64_t ExtraUniformBindingSize = - AURORA_ALIGN(std::max({sizeof(BlurUniformBlock), sizeof(DropShadowUniformBlock), sizeof(ColorMatrixUniformBlock), - sizeof(GradientUniformBlock), sizeof(SeedResampleUniformBlock)}), + AURORA_ALIGN(std::max({sizeof(BlurUniformBlock), sizeof(DropShadowUniformBlock), sizeof(SimpleFilterUniformBlock), + sizeof(GradientUniforzxmBlock), sizeof(SeedResampleUniformBlock)}), 16); constexpr std::string_view vertexSource = R"( @@ -367,20 +367,21 @@ fn main(@builtin(position) position: vec4, @location(0) uv: vec2) -> @ } )"sv; -constexpr std::string_view colorMatrixFragmentSource = R"( -struct ColorMatrixUniforms { +constexpr std::string_view simpleFilterFragmentSource = R"( +struct SimpleFilterUniforms { matrix: mat4x4, + opacity: vec4, }; @group(0) @binding(1) var s: sampler; @group(1) @binding(0) var t: texture_2d; -@group(2) @binding(0) var color_matrix: ColorMatrixUniforms; +@group(2) @binding(0) var simple_filter: SimpleFilterUniforms; @fragment fn main(@location(0) uv: vec2) -> @location(0) vec4 { let tex_color = textureSample(t, s, uv); - let transformed_color = (color_matrix.matrix * tex_color).rgb; - return vec4(transformed_color, tex_color.a); + let transformed_color = simple_filter.matrix * tex_color; + return vec4(transformed_color.rgb, tex_color.a) * simple_filter.opacity.x; } )"sv; @@ -511,21 +512,6 @@ wgpu::BlendState blend_state(BlendMode mode) { .dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha, }, }; - case BlendMode::Opacity: - return { - .color = - { - .operation = wgpu::BlendOperation::Add, - .srcFactor = wgpu::BlendFactor::Constant, - .dstFactor = wgpu::BlendFactor::Zero, - }, - .alpha = - { - .operation = wgpu::BlendOperation::Add, - .srcFactor = wgpu::BlendFactor::Constant, - .dstFactor = wgpu::BlendFactor::Zero, - }, - }; case BlendMode::None: default: return {}; @@ -548,7 +534,7 @@ const wgpu::PipelineLayout create_pipeline_layout(PipelineKind kind) { case PipelineKind::Blur: case PipelineKind::RegionBlit: case PipelineKind::DropShadow: - case PipelineKind::ColorMatrix: + case PipelineKind::SimpleFilter: case PipelineKind::SeedResample: layouts[layoutCount++] = g_imageBindGroupLayout; layouts[layoutCount++] = g_uniformBindGroupLayout; @@ -556,7 +542,6 @@ const wgpu::PipelineLayout create_pipeline_layout(PipelineKind kind) { case PipelineKind::Geometry: case PipelineKind::Blit: case PipelineKind::OpaqueBlit: - case PipelineKind::Opacity: default: layouts[layoutCount++] = g_imageBindGroupLayout; break; @@ -585,12 +570,11 @@ const std::string_view fragment_source(PipelineKind kind) { return regionBlitFragmentSource; case PipelineKind::DropShadow: return dropShadowFragmentSource; - case PipelineKind::ColorMatrix: - return colorMatrixFragmentSource; + case PipelineKind::SimpleFilter: + return simpleFilterFragmentSource; case PipelineKind::MaskImage: return maskImageFragmentSource; case PipelineKind::Blit: - case PipelineKind::Opacity: default: return blitFragmentSource; } diff --git a/lib/rmlui/pipeline.hpp b/lib/rmlui/pipeline.hpp index 7ec451d..4c3c1e7 100644 --- a/lib/rmlui/pipeline.hpp +++ b/lib/rmlui/pipeline.hpp @@ -10,7 +10,7 @@ namespace aurora::rmlui { -constexpr uint32_t RmlPipelineConfigVersion = 1; +constexpr uint32_t RmlPipelineConfigVersion = 2; enum class PipelineKind : uint32_t { Geometry, @@ -18,11 +18,10 @@ enum class PipelineKind : uint32_t { Blit, OpaqueBlit, SeedResample, - Opacity, + SimpleFilter, Blur, RegionBlit, DropShadow, - ColorMatrix, MaskImage, }; @@ -43,7 +42,6 @@ enum class StencilMode : uint32_t { enum class BlendMode : uint32_t { None, Premultiplied, - Opacity, }; enum class DrawKind : uint32_t { diff --git a/lib/webgpu/gpu.cpp b/lib/webgpu/gpu.cpp index 143a95e..694343f 100644 --- a/lib/webgpu/gpu.cpp +++ b/lib/webgpu/gpu.cpp @@ -47,6 +47,7 @@ TextureWithSampler g_depthBuffer; // EFB -> XFB copy pipeline static wgpu::BindGroupLayout g_CopyBindGroupLayout; wgpu::RenderPipeline g_CopyPipeline; +wgpu::RenderPipeline g_CopyPremultipliedAlphaPipeline; wgpu::BindGroup g_CopyBindGroup; static AuroraSampler g_Resampler = SAMPLER_BILINEAR; static wgpu::BindGroupLayout g_ResampleBindGroupLayout; @@ -411,26 +412,21 @@ fn vs_main(@builtin(vertex_index) vtxIdx: u32) -> VertexOutput { } @fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { +fn fs_opaque(in: VertexOutput) -> @location(0) vec4 { let color = textureSample(efb_texture, efb_sampler, in.uv); return vec4(color.rgb, 1.0); } + +@fragment +fn fs_premultiplied_alpha(in: VertexOutput) -> @location(0) vec4 { + return textureSample(efb_texture, efb_sampler, in.uv); +} )"""; const wgpu::ShaderModuleDescriptor moduleDescriptor{ .nextInChain = &sourceDescriptor, .label = "XFB Copy Module", }; auto module = g_device.CreateShaderModule(&moduleDescriptor); - const std::array colorTargets{wgpu::ColorTargetState{ - .format = g_graphicsConfig.surfaceConfiguration.format, - .writeMask = wgpu::ColorWriteMask::All, - }}; - const wgpu::FragmentState fragmentState{ - .module = module, - .entryPoint = "fs_main", - .targetCount = colorTargets.size(), - .targets = colorTargets.data(), - }; const std::array bindGroupLayoutEntries{ wgpu::BindGroupLayoutEntry{ .binding = 0, @@ -460,25 +456,58 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { .bindGroupLayouts = &g_CopyBindGroupLayout, }; auto pipelineLayout = g_device.CreatePipelineLayout(&layoutDescriptor); - const wgpu::RenderPipelineDescriptor pipelineDescriptor{ - .layout = pipelineLayout, - .vertex = - wgpu::VertexState{ - .module = module, - .entryPoint = "vs_main", - }, - .primitive = - wgpu::PrimitiveState{ - .topology = wgpu::PrimitiveTopology::TriangleList, - }, - .multisample = - wgpu::MultisampleState{ - .count = 1, - .mask = UINT32_MAX, - }, - .fragment = &fragmentState, + + const auto make_copy_pipeline = [&](const char* label, const char* fragmentEntryPoint, const wgpu::BlendState* blend) { + const std::array colorTargets{wgpu::ColorTargetState{ + .format = g_graphicsConfig.surfaceConfiguration.format, + .blend = blend, + .writeMask = wgpu::ColorWriteMask::All, + }}; + const wgpu::FragmentState fragmentState{ + .module = module, + .entryPoint = fragmentEntryPoint, + .targetCount = colorTargets.size(), + .targets = colorTargets.data(), + }; + const wgpu::RenderPipelineDescriptor pipelineDescriptor{ + .label = label, + .layout = pipelineLayout, + .vertex = + wgpu::VertexState{ + .module = module, + .entryPoint = "vs_main", + }, + .primitive = + wgpu::PrimitiveState{ + .topology = wgpu::PrimitiveTopology::TriangleList, + }, + .multisample = + wgpu::MultisampleState{ + .count = 1, + .mask = UINT32_MAX, + }, + .fragment = &fragmentState, + }; + return g_device.CreateRenderPipeline(&pipelineDescriptor); }; - g_CopyPipeline = g_device.CreateRenderPipeline(&pipelineDescriptor); + g_CopyPipeline = make_copy_pipeline("XFB Copy Pipeline", "fs_opaque", nullptr); + + const wgpu::BlendState premultipliedAlphaBlend{ + .color = + { + .operation = wgpu::BlendOperation::Add, + .srcFactor = wgpu::BlendFactor::One, + .dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha, + }, + .alpha = + { + .operation = wgpu::BlendOperation::Add, + .srcFactor = wgpu::BlendFactor::One, + .dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha, + }, + }; + g_CopyPremultipliedAlphaPipeline = + make_copy_pipeline("XFB Premultiplied Alpha Copy Pipeline", "fs_premultiplied_alpha", &premultipliedAlphaBlend); } void create_resample_pipeline() { @@ -983,6 +1012,7 @@ void shutdown() { gpu_prof::shutdown(); g_CopyBindGroupLayout = {}; g_CopyPipeline = {}; + g_CopyPremultipliedAlphaPipeline = {}; g_CopyBindGroup = {}; g_ResampleBindGroupLayout = {}; g_ResamplePipeline = {}; diff --git a/lib/webgpu/gpu.hpp b/lib/webgpu/gpu.hpp index 7329fa6..0753052 100644 --- a/lib/webgpu/gpu.hpp +++ b/lib/webgpu/gpu.hpp @@ -48,6 +48,7 @@ extern TextureWithSampler g_frameBuffer; extern TextureWithSampler g_frameBufferResolved; extern TextureWithSampler g_depthBuffer; extern wgpu::RenderPipeline g_CopyPipeline; +extern wgpu::RenderPipeline g_CopyPremultipliedAlphaPipeline; extern wgpu::BindGroup g_CopyBindGroup; extern wgpu::Instance g_instance; extern wgpu::AdapterInfo g_adapterInfo;