From 312a33d27fe18fa1185919e387d653c5c0a61df4 Mon Sep 17 00:00:00 2001 From: wuggy Date: Thu, 17 Sep 2026 23:14:47 -0700 Subject: [PATCH] CWebRender initial commit --- gfx/layers/composite/ColorLayerComposite.cpp | 17 + gfx/layers/opengl/CompositorOGL.cpp | 146 ++++++ gfx/layers/opengl/CompositorOGL.h | 13 + gfx/moz.build | 1 + gfx/wr/moz.build | 13 + gfx/wr/webrender.c | 447 +++++++++++++++++++ gfx/wr/webrender.h | 100 +++++ 7 files changed, 737 insertions(+) create mode 100644 gfx/wr/moz.build create mode 100644 gfx/wr/webrender.c create mode 100644 gfx/wr/webrender.h diff --git a/gfx/layers/composite/ColorLayerComposite.cpp b/gfx/layers/composite/ColorLayerComposite.cpp index 4277a8f70a..03058b6b60 100644 --- a/gfx/layers/composite/ColorLayerComposite.cpp +++ b/gfx/layers/composite/ColorLayerComposite.cpp @@ -10,6 +10,7 @@ #include "mozilla/gfx/Rect.h" // for Rect #include "mozilla/gfx/Types.h" // for Color #include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorOGL.h" // for CompositorOGL #include "mozilla/layers/CompositorTypes.h" // for DiagnosticFlags::COLOR #include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc #include "mozilla/mozalloc.h" // for operator delete, etc @@ -28,6 +29,22 @@ ColorLayerComposite::RenderLayer(const IntRect& aClipRect) RenderWithAllMasks(this, mCompositor, aClipRect, [&](EffectChain& effectChain, const IntRect& clipRect) { GenEffectChain(effectChain); + + // Route simple 2D color primitives through the retained C display list. + // Masked or 3D layers continue through the established effect path until + // equivalent WebRender primitives are available for those cases. + CompositorOGL* compositorOGL = mCompositor->AsCompositorOGL(); + if (compositorOGL && + !effectChain.mSecondaryEffects[EffectTypes::MASK] && + GetEffectiveMixBlendMode() == CompositionOp::OP_OVER && + transform.Is2D() && !clipRect.IsEmpty()) { + if (compositorOGL->DrawWebRenderRect(rect, GetColor(), + GetEffectiveOpacity(), transform, + clipRect)) { + return; + } + } + mCompositor->DrawQuad(rect, clipRect, effectChain, GetEffectiveOpacity(), transform); }); diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index e51839d190..78bf256360 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -86,6 +86,7 @@ CompositorOGL::CompositorOGL(CompositorBridgeParent* aParent, : Compositor(aWidget, aParent) , mWidgetSize(-1, -1) , mSurfaceSize(aSurfaceWidth, aSurfaceHeight) + , mWebRenderContext(nullptr) , mHasBGRA(0) , mUseExternalSurfaceSize(aUseExternalSurfaceSize) , mFrameInProgress(false) @@ -165,6 +166,11 @@ CompositorOGL::Destroy() void CompositorOGL::CleanupResources() { + if (mWebRenderContext) { + wr_context_destroy(mWebRenderContext); + mWebRenderContext = nullptr; + } + if (!mGLContext) return; @@ -994,6 +1000,146 @@ CompositorOGL::DrawTriangle(const gfx::TexturedTriangle& aTriangle, aOpacity, aTransform, aVisibleRect); } +bool +CompositorOGL::DrawWebRenderContext(wr_context* aContext, + const IntRect& aClipRect) +{ + if (!aContext) { + return false; + } + + const wr_frame* frame = wr_context_build_frame(aContext); + if (frame) { + DrawWebRenderFrame(*frame, aClipRect); + return true; + } + return false; +} + +bool +CompositorOGL::DrawWebRenderRect(const Rect& aRect, + const Color& aColor, + Float aOpacity, + const Matrix4x4& aTransform, + const IntRect& aClipRect) +{ + if (!mFrameInProgress || aClipRect.IsEmpty() || !aTransform.Is2D()) { + return false; + } + if (!mWebRenderContext) { + mWebRenderContext = wr_context_create(8); + } + if (!mWebRenderContext) { + return false; + } + + wr_context_clear(mWebRenderContext); + wr_context_set_viewport(mWebRenderContext, + wr_rect{ Float(aClipRect.x), Float(aClipRect.y), + Float(aClipRect.width), Float(aClipRect.height) }); + Matrix transform = aTransform.As2D(); + wr_context_set_transform(mWebRenderContext, + wr_transform{ transform._11, transform._12, + transform._21, transform._22, + transform._31, transform._32 }); + wr_context_set_opacity(mWebRenderContext, aOpacity); + if (!wr_display_list_push_rect(mWebRenderContext, + wr_rect{ aRect.x, aRect.y, + aRect.width, aRect.height }, + wr_color{ aColor.r, aColor.g, + aColor.b, aColor.a })) { + return false; + } + + bool drawn = DrawWebRenderContext(mWebRenderContext, aClipRect); + wr_context_clear(mWebRenderContext); + return drawn; +} + +void +CompositorOGL::DrawWebRenderFrame(const wr_frame& aFrame, + const IntRect& aClipRect) +{ + MOZ_ASSERT(mFrameInProgress, "frame not started"); + MOZ_ASSERT(mCurrentRenderTarget, "No destination"); + + if (!aFrame.quads || !aFrame.batches || + aFrame.quad_count == 0 || aFrame.batch_count == 0) { + return; + } + + MakeCurrent(); + + // This first adapter handles solid rectangles. It deliberately reuses the + // compositor's existing four-rect uniform and triangle VBO path, so the + // retained C display list is submitted to the GPU without a CPU raster pass. + EffectSolidColor effect(Color(0, 0, 0, 0)); + ShaderConfigOGL config = GetShaderConfigFor(&effect); + ShaderProgramOGL* program = GetShaderProgramFor(config); + if (!program) { + return; + } + + IntPoint offset = mCurrentRenderTarget->GetOrigin(); + ActivateProgram(program); + program->SetProjectionMatrix(mProjMatrix); + program->SetLayerTransform(Matrix4x4()); + program->SetRenderOffset(offset.x, offset.y); + + ScopedGLState scopedScissorTestState(mGLContext, LOCAL_GL_SCISSOR_TEST, true); + ScopedScissorRect autoScissorRect(mGLContext, + aClipRect.x, + FlipY(aClipRect.y + aClipRect.height), + aClipRect.width, + aClipRect.height); + + for (size_t batchIndex = 0; batchIndex < aFrame.batch_count; ++batchIndex) { + const wr_gpu_batch& batch = aFrame.batches[batchIndex]; + if (batch.first_quad > aFrame.quad_count || + batch.quad_count > aFrame.quad_count - batch.first_quad) { + continue; + } + + IntRect batchClip = aClipRect; + if (batch.has_clip) { + IntRect primitiveClip(int32_t(batch.clip_rect.x), + int32_t(batch.clip_rect.y), + int32_t(batch.clip_rect.width), + int32_t(batch.clip_rect.height)); + batchClip.IntersectRect(batchClip, primitiveClip); + if (batchClip.IsEmpty()) { + continue; + } + } + ScopedScissorRect batchScissor( + mGLContext, batchClip.x, FlipY(batchClip.y + batchClip.height), + batchClip.width, batchClip.height); + + program->SetLayerTransform(Matrix4x4::From2D( + Matrix(batch.transform.m11, batch.transform.m12, + batch.transform.m21, batch.transform.m22, + batch.transform.m31, batch.transform.m32))); + + uint32_t remaining = batch.quad_count; + uint32_t quadIndex = batch.first_quad; + program->SetRenderColor(Color(batch.color.r, batch.color.g, + batch.color.b, batch.color.a)); + + while (remaining) { + Rect rects[4] = {}; + int count = remaining > 4 ? 4 : int(remaining); + for (int i = 0; i < count; ++i) { + const wr_gpu_quad& quad = aFrame.quads[quadIndex + i]; + rects[i] = Rect(quad.rect.x, quad.rect.y, + quad.rect.width, quad.rect.height); + } + BindAndDrawQuads(program, count, rects, nullptr); + quadIndex += count; + remaining -= count; + } + } +} + template void CompositorOGL::DrawGeometry(const Geometry& aGeometry, diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h index da204b12e6..c446ed7593 100644 --- a/gfx/layers/opengl/CompositorOGL.h +++ b/gfx/layers/opengl/CompositorOGL.h @@ -22,6 +22,7 @@ #include "mozilla/gfx/Rect.h" // for Rect, IntRect #include "mozilla/gfx/Triangle.h" // for Triangle #include "mozilla/gfx/Types.h" // for Float, SurfaceFormat, etc +#include "mozilla/gfx/webrender.h" // for wr_frame #include "mozilla/layers/Compositor.h" // for SurfaceInitMode, Compositor, etc #include "mozilla/layers/CompositorTypes.h" // for MaskType::MaskType::NumMaskTypes, etc #include "mozilla/layers/LayersTypes.h" @@ -211,6 +212,17 @@ public: const gfx::Matrix4x4& aTransform, const gfx::Rect& aVisibleRect) override; + // Consume solid retained-display-list quads through the existing GPU path. + bool DrawWebRenderContext(wr_context* aContext, + const gfx::IntRect& aClipRect); + bool DrawWebRenderRect(const gfx::Rect& aRect, + const gfx::Color& aColor, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::IntRect& aClipRect); + void DrawWebRenderFrame(const wr_frame& aFrame, + const gfx::IntRect& aClipRect); + virtual void EndFrame() override; virtual void EndFrameForExternalComposition(const gfx::Matrix& aTransform) override; @@ -327,6 +339,7 @@ private: /** Currently bound render target */ RefPtr mCurrentRenderTarget; + wr_context* mWebRenderContext; #ifdef DEBUG CompositingRenderTargetOGL* mWindowRenderTarget; #endif diff --git a/gfx/moz.build b/gfx/moz.build index a102159cb9..abaa45ae19 100644 --- a/gfx/moz.build +++ b/gfx/moz.build @@ -8,6 +8,7 @@ if CONFIG['MOZ_TREE_CAIRO']: DIRS += [ '2d', + 'wr', 'ycbcr', 'angle', 'src', diff --git a/gfx/wr/moz.build b/gfx/wr/moz.build new file mode 100644 index 0000000000..0720c47210 --- /dev/null +++ b/gfx/wr/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- + +# The retained display-list core is deliberately C-only. GPU backends can +# consume the emitted instanced-quad stream through a stable C ABI. +EXPORTS.mozilla.gfx += [ + 'webrender.h', +] + +SOURCES += [ + 'webrender.c', +] + +FINAL_LIBRARY = 'xul' diff --git a/gfx/wr/webrender.c b/gfx/wr/webrender.c new file mode 100644 index 0000000000..0d0fdbce23 --- /dev/null +++ b/gfx/wr/webrender.c @@ -0,0 +1,447 @@ +#include "webrender.h" + +#include +#include +#include + +typedef struct { + wr_rect rect; + wr_color color; + wr_transform transform; + wr_rect clip_rect; + uint8_t has_clip; +} wr_rect_command; + +typedef struct { + wr_transform transform; + float opacity; + size_t clip_depth; +} wr_display_state; + +struct wr_context { + wr_rect_command* commands; + size_t command_count; + size_t command_capacity; + + wr_gpu_quad* quads; + size_t quad_count; + size_t quad_capacity; + + wr_gpu_batch* batches; + size_t batch_count; + size_t batch_capacity; + + wr_rect viewport; + wr_transform transform; + float opacity; + wr_rect* clip_stack; + size_t clip_depth; + size_t clip_capacity; + wr_display_state* state_stack; + size_t state_depth; + size_t state_capacity; + wr_frame frame; +}; + +static int +wr_size_mul_overflow(size_t a, size_t b) +{ + return b != 0 && a > SIZE_MAX / b; +} + +static int +wr_reserve(void** data, size_t* capacity, size_t count, size_t element_size) +{ + size_t new_capacity; + void* new_data; + + if (count <= *capacity) + return 1; + if (element_size == 0 || wr_size_mul_overflow(count, element_size)) + return 0; + + new_capacity = *capacity ? *capacity : 8; + while (new_capacity < count) { + if (new_capacity > SIZE_MAX / 2) { + new_capacity = count; + break; + } + new_capacity *= 2; + } + if (wr_size_mul_overflow(new_capacity, element_size)) + return 0; + + new_data = realloc(*data, new_capacity * element_size); + if (!new_data) + return 0; + + *data = new_data; + *capacity = new_capacity; + return 1; +} + +static int +wr_valid_rect(wr_rect rect) +{ + return isfinite(rect.x) && isfinite(rect.y) && + isfinite(rect.width) && isfinite(rect.height) && + rect.width > 0.0f && rect.height > 0.0f; +} + +static int +wr_valid_transform(wr_transform transform) +{ + return isfinite(transform.m11) && isfinite(transform.m12) && + isfinite(transform.m21) && isfinite(transform.m22) && + isfinite(transform.m31) && isfinite(transform.m32); +} + +static wr_transform +wr_identity_transform(void) +{ + wr_transform transform = { 1, 0, 0, 1, 0, 0 }; + return transform; +} + +static wr_transform +wr_multiply_transform(wr_transform a, wr_transform b) +{ + wr_transform result; + result.m11 = a.m11 * b.m11 + a.m21 * b.m12; + result.m12 = a.m12 * b.m11 + a.m22 * b.m12; + result.m21 = a.m11 * b.m21 + a.m21 * b.m22; + result.m22 = a.m12 * b.m21 + a.m22 * b.m22; + result.m31 = a.m11 * b.m31 + a.m21 * b.m32 + a.m31; + result.m32 = a.m12 * b.m31 + a.m22 * b.m32 + a.m32; + return result; +} + +static wr_rect +wr_empty_rect(void) +{ + wr_rect rect = { 0, 0, 0, 0 }; + return rect; +} + +static float +wr_clamp01(float value) +{ + if (value < 0.0f) + return 0.0f; + if (value > 1.0f) + return 1.0f; + return value; +} + +static wr_color +wr_normalize_color(wr_color color) +{ + color.r = wr_clamp01(color.r); + color.g = wr_clamp01(color.g); + color.b = wr_clamp01(color.b); + color.a = wr_clamp01(color.a); + return color; +} + +static int +wr_colors_equal(wr_color a, wr_color b) +{ + return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; +} + +static int +wr_rects_equal(wr_rect a, wr_rect b) +{ + return a.x == b.x && a.y == b.y && + a.width == b.width && a.height == b.height; +} + +static int +wr_intersects(wr_rect a, wr_rect b) +{ + return a.x < b.x + b.width && a.x + a.width > b.x && + a.y < b.y + b.height && a.y + a.height > b.y; +} + +static wr_rect +wr_intersect_rect(wr_rect a, wr_rect b) +{ + float left = a.x > b.x ? a.x : b.x; + float top = a.y > b.y ? a.y : b.y; + float right = a.x + a.width < b.x + b.width ? + a.x + a.width : b.x + b.width; + float bottom = a.y + a.height < b.y + b.height ? + a.y + a.height : b.y + b.height; + wr_rect result = { left, top, right - left, bottom - top }; + return result; +} + +static wr_rect +wr_transform_bounds(wr_rect rect, wr_transform transform) +{ + float x1 = rect.x * transform.m11 + rect.y * transform.m21 + transform.m31; + float y1 = rect.x * transform.m12 + rect.y * transform.m22 + transform.m32; + float x2 = (rect.x + rect.width) * transform.m11 + + rect.y * transform.m21 + transform.m31; + float y2 = (rect.x + rect.width) * transform.m12 + + rect.y * transform.m22 + transform.m32; + float x3 = rect.x * transform.m11 + + (rect.y + rect.height) * transform.m21 + transform.m31; + float y3 = rect.x * transform.m12 + + (rect.y + rect.height) * transform.m22 + transform.m32; + float x4 = (rect.x + rect.width) * transform.m11 + + (rect.y + rect.height) * transform.m21 + transform.m31; + float y4 = (rect.x + rect.width) * transform.m12 + + (rect.y + rect.height) * transform.m22 + transform.m32; + float left = fminf(fminf(x1, x2), fminf(x3, x4)); + float right = fmaxf(fmaxf(x1, x2), fmaxf(x3, x4)); + float top = fminf(fminf(y1, y2), fminf(y3, y4)); + float bottom = fmaxf(fmaxf(y1, y2), fmaxf(y3, y4)); + wr_rect result = { left, top, right - left, bottom - top }; + return result; +} + +wr_context* +wr_context_create(size_t initial_capacity) +{ + wr_context* context = (wr_context*)calloc(1, sizeof(*context)); + if (!context) + return NULL; + + context->viewport.x = -FLT_MAX; + context->viewport.y = -FLT_MAX; + context->viewport.width = FLT_MAX; + context->viewport.height = FLT_MAX; + context->transform = wr_identity_transform(); + context->opacity = 1.0f; + + if (initial_capacity && + !wr_reserve((void**)&context->commands, &context->command_capacity, + initial_capacity, sizeof(*context->commands))) { + wr_context_destroy(context); + return NULL; + } + return context; +} + +void +wr_context_destroy(wr_context* context) +{ + if (!context) + return; + free(context->commands); + free(context->quads); + free(context->batches); + free(context->clip_stack); + free(context->state_stack); + free(context); +} + +void +wr_context_clear(wr_context* context) +{ + if (!context) + return; + context->command_count = 0; + context->quad_count = 0; + context->batch_count = 0; + context->clip_depth = 0; + context->state_depth = 0; +} + +void +wr_context_set_viewport(wr_context* context, wr_rect viewport) +{ + if (context && wr_valid_rect(viewport)) + context->viewport = viewport; +} + +void +wr_context_set_transform(wr_context* context, wr_transform transform) +{ + if (context && wr_valid_transform(transform)) + context->transform = transform; +} + +void +wr_context_set_opacity(wr_context* context, float opacity) +{ + if (context && isfinite(opacity)) { + context->opacity = wr_clamp01(opacity); + } +} + +int +wr_display_list_push_stacking_context(wr_context* context, + wr_transform transform, + float opacity) +{ + wr_display_state state; + + if (!context || !wr_valid_transform(transform) || !isfinite(opacity) || + context->state_depth == SIZE_MAX) + return 0; + if (!wr_reserve((void**)&context->state_stack, &context->state_capacity, + context->state_depth + 1, sizeof(*context->state_stack))) + return 0; + + state.transform = context->transform; + state.opacity = context->opacity; + state.clip_depth = context->clip_depth; + context->state_stack[context->state_depth++] = state; + context->transform = wr_multiply_transform(context->transform, transform); + context->opacity = wr_clamp01(context->opacity * wr_clamp01(opacity)); + return 1; +} + +void +wr_display_list_pop_stacking_context(wr_context* context) +{ + wr_display_state state; + + if (!context || !context->state_depth) + return; + state = context->state_stack[--context->state_depth]; + context->transform = state.transform; + context->opacity = state.opacity; + context->clip_depth = state.clip_depth; +} + +int +wr_display_list_push_clip(wr_context* context, wr_rect clip) +{ + wr_rect effective_clip; + + if (!context || !wr_valid_rect(clip) || + context->clip_depth == SIZE_MAX) + return 0; + if (!wr_reserve((void**)&context->clip_stack, &context->clip_capacity, + context->clip_depth + 1, sizeof(*context->clip_stack))) + return 0; + + effective_clip = clip; + if (context->clip_depth) + effective_clip = wr_intersect_rect( + context->clip_stack[context->clip_depth - 1], clip); + context->clip_stack[context->clip_depth++] = effective_clip; + return 1; +} + +void +wr_display_list_pop_clip(wr_context* context) +{ + if (context && context->clip_depth) + --context->clip_depth; +} + +int +wr_display_list_push_rect(wr_context* context, wr_rect rect, wr_color color) +{ + return context ? wr_display_list_push_transformed_rect( + context, rect, color, context->transform) : 0; +} + +int +wr_display_list_push_transformed_rect(wr_context* context, wr_rect rect, + wr_color color, wr_transform transform) +{ + wr_rect clip_rect = context && context->clip_depth ? + context->clip_stack[context->clip_depth - 1] : + context ? context->viewport : wr_empty_rect(); + uint8_t has_clip = context && context->clip_depth ? 1 : 0; + + if (!context || !wr_valid_rect(rect) || !wr_valid_transform(transform)) + return 0; + if (context->command_count == SIZE_MAX || + context->command_count >= UINT32_MAX) + return 0; + if (!wr_reserve((void**)&context->commands, &context->command_capacity, + context->command_count + 1, sizeof(*context->commands))) + return 0; + + context->commands[context->command_count].rect = rect; + color = wr_normalize_color(color); + color.a *= context->opacity; + context->commands[context->command_count].color = color; + context->commands[context->command_count].transform = transform; + context->commands[context->command_count].clip_rect = clip_rect; + context->commands[context->command_count].has_clip = has_clip; + ++context->command_count; + return 1; +} + +const wr_frame* +wr_context_build_frame(wr_context* context) +{ + size_t i; + + if (!context) + return NULL; + context->quad_count = 0; + context->batch_count = 0; + + for (i = 0; i < context->command_count; ++i) { + wr_rect_command* command = &context->commands[i]; + wr_gpu_quad* quad; + wr_gpu_batch* batch; + + if (!wr_intersects(wr_transform_bounds(command->rect, + command->transform), + context->viewport) || + (command->clip_rect.width <= 0.0f || + command->clip_rect.height <= 0.0f)) + continue; + if (context->quad_count == SIZE_MAX || + context->quad_count >= UINT32_MAX || + context->batch_count == SIZE_MAX || + context->batch_count >= UINT32_MAX) + return NULL; + if (!wr_reserve((void**)&context->quads, &context->quad_capacity, + context->quad_count + 1, sizeof(*context->quads)) || + !wr_reserve((void**)&context->batches, &context->batch_capacity, + context->batch_count + 1, sizeof(*context->batches))) + return NULL; + + quad = &context->quads[context->quad_count++]; + quad->rect = command->rect; + quad->color = command->color; + + if (context->batch_count && + wr_colors_equal(context->batches[context->batch_count - 1].color, + command->color) && + context->batches[context->batch_count - 1].transform.m11 == + command->transform.m11 && + context->batches[context->batch_count - 1].transform.m12 == + command->transform.m12 && + context->batches[context->batch_count - 1].transform.m21 == + command->transform.m21 && + context->batches[context->batch_count - 1].transform.m22 == + command->transform.m22 && + context->batches[context->batch_count - 1].transform.m31 == + command->transform.m31 && + context->batches[context->batch_count - 1].transform.m32 == + command->transform.m32 && + context->batches[context->batch_count - 1].has_clip == + command->has_clip && + (!command->has_clip || + wr_rects_equal(context->batches[context->batch_count - 1].clip_rect, + command->clip_rect))) { + batch = &context->batches[context->batch_count - 1]; + ++batch->quad_count; + } else { + batch = &context->batches[context->batch_count++]; + batch->first_quad = (uint32_t)(context->quad_count - 1); + batch->quad_count = 1; + batch->color = command->color; + batch->transform = command->transform; + batch->clip_rect = command->clip_rect; + batch->has_clip = command->has_clip; + } + } + + context->frame.quads = context->quads; + context->frame.quad_count = context->quad_count; + context->frame.batches = context->batches; + context->frame.batch_count = context->batch_count; + return &context->frame; +} diff --git a/gfx/wr/webrender.h b/gfx/wr/webrender.h new file mode 100644 index 0000000000..1726e9233b --- /dev/null +++ b/gfx/wr/webrender.h @@ -0,0 +1,100 @@ +#ifndef gfx_webrender_h +#define gfx_webrender_h + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct wr_context wr_context; + +typedef struct { + float x; + float y; + float width; + float height; +} wr_rect; + +typedef struct { + float r; + float g; + float b; + float a; +} wr_color; + +/* A 2D affine transform in the same layout used by gfx::Matrix. */ +typedef struct { + float m11; + float m12; + float m21; + float m22; + float m31; + float m32; +} wr_transform; + +typedef struct { + wr_rect rect; + wr_color color; +} wr_gpu_quad; + +typedef struct { + uint32_t first_quad; + uint32_t quad_count; + wr_color color; + wr_transform transform; + wr_rect clip_rect; + uint8_t has_clip; +} wr_gpu_batch; + +typedef struct { + const wr_gpu_quad* quads; + size_t quad_count; + const wr_gpu_batch* batches; + size_t batch_count; +} wr_frame; + +/* Creates a retained display-list context. */ +wr_context* wr_context_create(size_t initial_capacity); +void wr_context_destroy(wr_context* context); + +/* Removes every item from the retained display list. */ +void wr_context_clear(wr_context* context); + +/* Sets the viewport used for coarse CPU-side culling. */ +void wr_context_set_viewport(wr_context* context, wr_rect viewport); + +/* Sets the transform applied to subsequently recorded primitives. */ +void wr_context_set_transform(wr_context* context, wr_transform transform); + +/* Sets the opacity applied to subsequently recorded primitives. */ +void wr_context_set_opacity(wr_context* context, float opacity); + +/* Records a retained stacking-context boundary and restores it on pop. */ +int wr_display_list_push_stacking_context(wr_context* context, + wr_transform transform, + float opacity); +void wr_display_list_pop_stacking_context(wr_context* context); + +/* Pushes/pops a retained primitive clip. Clips are intersected in order. */ +int wr_display_list_push_clip(wr_context* context, wr_rect clip); +void wr_display_list_pop_clip(wr_context* context); + +/* Adds an opaque or translucent solid rectangle to the display list. */ +int wr_display_list_push_rect(wr_context* context, wr_rect rect, + wr_color color); + +/* Explicit transform form for display-list builders. */ +int wr_display_list_push_transformed_rect(wr_context* context, wr_rect rect, + wr_color color, + wr_transform transform); + +/* Builds the GPU-ready stream from the retained display list. */ +const wr_frame* wr_context_build_frame(wr_context* context); + +#ifdef __cplusplus +} +#endif + +#endif /* gfx_webrender_h */