diff --git a/README.md b/README.md index e7cacf4..8a87f29 100644 --- a/README.md +++ b/README.md @@ -362,7 +362,7 @@ Notes: Use `NfClipContent` when a node needs normal clipping semantics. It renders a mask and applies it to the node's content, which is flexible but can force the backend to flush queued draws around the mask pass. -Use `NfRectMaskContent` when the mask shape is just the node's rounded rectangle and the content is small leaf-style UI content, such as cells in a list/table, clipped buttons, pills, badges, or compact panels. On Metal this is evaluated as a per-fragment rounded-rect SDF mask, so it can avoid the extra mask pass for the first active rect mask and keep more draw work batched. Other backends fall back to the normal mask behavior, so the flag is safe to use before every backend has a fast implementation. +Use `NfRectMaskContent` when the mask shape is just the node's rounded rectangle and the content is small leaf-style UI content, such as cells in a list/table, clipped buttons, pills, badges, or compact panels. Metal, OpenGL, and Vulkan evaluate the first active rect masks as per-fragment rounded-rect SDF masks, so they can avoid extra mask passes and keep more draw work batched. The number of fast rect masks is fixed at compile time with `-d:FigDrawFastRectMaskLimit=0`, `1`, or `2`; the default is `2`. Deeper rect masks fall back to normal mask behavior. `NfRectMaskContent` also composes with `NfClipContent`: a scroll viewport can use `NfClipContent`, while each small child item inside it uses `NfRectMaskContent`. diff --git a/src/figdraw/figbackend.nim b/src/figdraw/figbackend.nim index 8707adb..75a6ddb 100644 --- a/src/figdraw/figbackend.nim +++ b/src/figdraw/figbackend.nim @@ -44,6 +44,11 @@ type SdfMode* {.pure.} = enum sdfModeMtsdfAnnular = 16 sdfModeBackdropBlur = 17 +const FigDrawFastRectMaskLimit* {.intdefine.}: int = 2 +static: + doAssert FigDrawFastRectMaskLimit in 0 .. 2, + "FigDrawFastRectMaskLimit must be 0, 1, or 2" + type BackendFillKind* = enum bfColor diff --git a/src/figdraw/metal/metal_context.nim b/src/figdraw/metal/metal_context.nim index 4669058..5569832 100644 --- a/src/figdraw/metal/metal_context.nim +++ b/src/figdraw/metal/metal_context.nim @@ -27,6 +27,13 @@ proc round*(v: Vec2): Vec2 = const quadLimit = 10_921 const maxFramesInFlight = 3 +const fastRectMaskLimit = figbackend.FigDrawFastRectMaskLimit +when fastRectMaskLimit >= 2: + const rectMaskUniformBufferIndex = 17 +else: + const rectMaskUniformBufferIndex = 13 +const metalShaderDefines = + "#define FIGDRAW_FAST_RECT_MASK_LIMIT " & $fastRectMaskLimit & "\n" type PassKind = enum pkNone @@ -74,6 +81,15 @@ type FlushBuffers = object rectMaskMatXCapacity: int rectMaskMatY: ObjcOwned[MTLBuffer] rectMaskMatYCapacity: int + when fastRectMaskLimit >= 2: + rectMaskParams2: ObjcOwned[MTLBuffer] + rectMaskParams2Capacity: int + rectMaskRadii2: ObjcOwned[MTLBuffer] + rectMaskRadii2Capacity: int + rectMaskMatX2: ObjcOwned[MTLBuffer] + rectMaskMatX2Capacity: int + rectMaskMatY2: ObjcOwned[MTLBuffer] + rectMaskMatY2Capacity: int type FrameArena = object flushBuffers: seq[FlushBuffers] @@ -139,6 +155,11 @@ type MetalContext* = ref object of figbackend.BackendContext # Metal objects rectMaskRadii: tuple[buffer: ObjcOwned[MTLBuffer], data: seq[float32]] rectMaskMatX: tuple[buffer: ObjcOwned[MTLBuffer], data: seq[float32]] rectMaskMatY: tuple[buffer: ObjcOwned[MTLBuffer], data: seq[float32]] + when fastRectMaskLimit >= 2: + rectMaskParams2: tuple[buffer: ObjcOwned[MTLBuffer], data: seq[float32]] + rectMaskRadii2: tuple[buffer: ObjcOwned[MTLBuffer], data: seq[float32]] + rectMaskMatX2: tuple[buffer: ObjcOwned[MTLBuffer], data: seq[float32]] + rectMaskMatY2: tuple[buffer: ObjcOwned[MTLBuffer], data: seq[float32]] rectMaskStack: seq[RectMask] # SDF shader uniform (global) @@ -407,7 +428,7 @@ proc ensureDeviceAndPipelines(ctx: MetalContext) = raise newException(ValueError, "Failed to create Metal command queue") ctx.queue.resetRetained(q) - let shaderSource = metalShaderSource + let shaderSource = metalShaderDefines & metalShaderSource var err: NSError let library = fromRetained( @@ -597,6 +618,27 @@ proc upload(ctx: MetalContext) = ctx.rectMaskMatY.data, vertexCount * 4 * sizeof(float32), ) + when fastRectMaskLimit >= 2: + copyToBuf( + ctx.rectMaskParams2.buffer.borrow, + ctx.rectMaskParams2.data, + vertexCount * 4 * sizeof(float32), + ) + copyToBuf( + ctx.rectMaskRadii2.buffer.borrow, + ctx.rectMaskRadii2.data, + vertexCount * 4 * sizeof(float32), + ) + copyToBuf( + ctx.rectMaskMatX2.buffer.borrow, + ctx.rectMaskMatX2.data, + vertexCount * 4 * sizeof(float32), + ) + copyToBuf( + ctx.rectMaskMatY2.buffer.borrow, + ctx.rectMaskMatY2.data, + vertexCount * 4 * sizeof(float32), + ) proc grow(ctx: MetalContext) = ctx.flush() @@ -751,6 +793,16 @@ proc setRectMaskVert4(ctx: MetalContext, offset: int, params, radii, matX, matY: ctx.rectMaskMatX.data.setVert4(offset + i, matX) ctx.rectMaskMatY.data.setVert4(offset + i, matY) +when fastRectMaskLimit >= 2: + proc setRectMask2Vert4( + ctx: MetalContext, offset: int, params, radii, matX, matY: Vec4 + ) = + for i in 0 ..< 4: + ctx.rectMaskParams2.data.setVert4(offset + i, params) + ctx.rectMaskRadii2.data.setVert4(offset + i, radii) + ctx.rectMaskMatX2.data.setVert4(offset + i, matX) + ctx.rectMaskMatY2.data.setVert4(offset + i, matY) + proc setDisabledRectMaskVerts(ctx: MetalContext, firstVertex, vertexCount: int) = let params = vec4(0.0'f32, 0.0'f32, -1.0'f32, -1.0'f32) @@ -760,40 +812,77 @@ proc setDisabledRectMaskVerts(ctx: MetalContext, firstVertex, vertexCount: int) ctx.rectMaskRadii.data.setVert4(i, zero4) ctx.rectMaskMatX.data.setVert4(i, zero4) ctx.rectMaskMatY.data.setVert4(i, zero4) + when fastRectMaskLimit >= 2: + ctx.rectMaskParams2.data.setVert4(i, params) + ctx.rectMaskRadii2.data.setVert4(i, zero4) + ctx.rectMaskMatX2.data.setVert4(i, zero4) + ctx.rectMaskMatY2.data.setVert4(i, zero4) + +proc fastRectMaskCount(ctx: MetalContext): int = + for rectMask in ctx.rectMaskStack: + if rectMask.kind == rmkFast: + inc result proc setRectMaskVert4(ctx: MetalContext, offset: int) = - if ctx.maskBegun: + when fastRectMaskLimit == 0: return + else: + if ctx.maskBegun: + return - var - hasRectMask = false - params = vec4(0.0'f32) - radii = vec4(0.0'f32) - matX = vec4(0.0'f32) - matY = vec4(0.0'f32) - - if ctx.rectMaskStack.len > 0: - for i in countdown(ctx.rectMaskStack.len - 1, 0): - let rectMask = ctx.rectMaskStack[i] - if rectMask.kind == rmkFast: - hasRectMask = true - params = rectMask.params - radii = rectMask.radii - matX = rectMask.matX - matY = rectMask.matY - break - - if hasRectMask: - if not ctx.batchHasRectMask: - ctx.setDisabledRectMaskVerts(0, offset) - ctx.batchHasRectMask = true - ctx.setRectMaskVert4(offset, params, radii, matX, matY) - elif ctx.batchHasRectMask: - ctx.setDisabledRectMaskVerts(offset, 4) + var + maskCount = 0 + params = vec4(0.0'f32) + radii = vec4(0.0'f32) + matX = vec4(0.0'f32) + matY = vec4(0.0'f32) + when fastRectMaskLimit >= 2: + var + params2 = vec4(0.0'f32) + radii2 = vec4(0.0'f32) + matX2 = vec4(0.0'f32) + matY2 = vec4(0.0'f32) + + if ctx.rectMaskStack.len > 0: + for i in countdown(ctx.rectMaskStack.len - 1, 0): + let rectMask = ctx.rectMaskStack[i] + if rectMask.kind == rmkFast: + if maskCount == 0: + params = rectMask.params + radii = rectMask.radii + matX = rectMask.matX + matY = rectMask.matY + inc maskCount + elif fastRectMaskLimit >= 2 and maskCount == 1: + when fastRectMaskLimit >= 2: + params2 = rectMask.params + radii2 = rectMask.radii + matX2 = rectMask.matX + matY2 = rectMask.matY + inc maskCount + if maskCount >= fastRectMaskLimit: + break + + if maskCount > 0: + if not ctx.batchHasRectMask: + ctx.setDisabledRectMaskVerts(0, offset) + ctx.batchHasRectMask = true + ctx.setRectMaskVert4(offset, params, radii, matX, matY) + when fastRectMaskLimit >= 2: + if maskCount >= 2: + ctx.setRectMask2Vert4(offset, params2, radii2, matX2, matY2) + else: + let + disabledParams = vec4(0.0'f32, 0.0'f32, -1.0'f32, -1.0'f32) + zero4 = vec4(0.0'f32) + ctx.setRectMask2Vert4(offset, disabledParams, zero4, zero4, zero4) + elif ctx.batchHasRectMask: + ctx.setDisabledRectMaskVerts(offset, 4) template setRectMaskVert4IfNeeded(ctx: MetalContext, offset: int) = - if not ctx.maskBegun and (ctx.batchHasRectMask or ctx.rectMaskStack.len > 0): - ctx.setRectMaskVert4(offset) + when fastRectMaskLimit > 0: + if not ctx.maskBegun and (ctx.batchHasRectMask or ctx.rectMaskStack.len > 0): + ctx.setRectMaskVert4(offset) func `*`*(m: Mat4, v: Vec2): Vec2 = (m * vec3(v.x, v.y, 0.0)).xy @@ -1554,7 +1643,8 @@ method beginRectMask*( assert ctx.frameBegun == true, "ctx.beginFrame has not been called." assert ctx.maskBegun == false, "ctx.beginRectMask cannot start inside a mask." - if ctx.rectMaskStack.len == 0 and maskRect.w > 0.0'f32 and maskRect.h > 0.0'f32: + if fastRectMaskLimit > 0 and ctx.fastRectMaskCount() < fastRectMaskLimit and + maskRect.w > 0.0'f32 and maskRect.h > 0.0'f32: ctx.rectMaskStack.add(ctx.makeRectMask(maskRect, radii)) else: ctx.beginMask(maskRect, radii) @@ -1925,6 +2015,27 @@ proc flush(ctx: MetalContext, maskTextureRead: int = ctx.maskTextureWrite) = flushBuffers[].rectMaskMatYCapacity, rectMaskMatYBytes, ) + when fastRectMaskLimit >= 2: + ctx.ensureFlushBufferCapacity( + flushBuffers[].rectMaskParams2, + flushBuffers[].rectMaskParams2Capacity, + rectMaskParamsBytes, + ) + ctx.ensureFlushBufferCapacity( + flushBuffers[].rectMaskRadii2, + flushBuffers[].rectMaskRadii2Capacity, + rectMaskRadiiBytes, + ) + ctx.ensureFlushBufferCapacity( + flushBuffers[].rectMaskMatX2, + flushBuffers[].rectMaskMatX2Capacity, + rectMaskMatXBytes, + ) + ctx.ensureFlushBufferCapacity( + flushBuffers[].rectMaskMatY2, + flushBuffers[].rectMaskMatY2Capacity, + rectMaskMatYBytes, + ) copyToBuf(flushBuffers[].positions.borrow, ctx.positions.data, positionsBytes) copyToBuf(flushBuffers[].uvs.borrow, ctx.uvs.data, uvsBytes) @@ -1957,6 +2068,23 @@ proc flush(ctx: MetalContext, maskTextureRead: int = ctx.maskTextureWrite) = copyToBuf( flushBuffers[].rectMaskMatY.borrow, ctx.rectMaskMatY.data, rectMaskMatYBytes ) + when fastRectMaskLimit >= 2: + copyToBuf( + flushBuffers[].rectMaskParams2.borrow, + ctx.rectMaskParams2.data, + rectMaskParamsBytes, + ) + copyToBuf( + flushBuffers[].rectMaskRadii2.borrow, + ctx.rectMaskRadii2.data, + rectMaskRadiiBytes, + ) + copyToBuf( + flushBuffers[].rectMaskMatX2.borrow, ctx.rectMaskMatX2.data, rectMaskMatXBytes + ) + copyToBuf( + flushBuffers[].rectMaskMatY2.borrow, ctx.rectMaskMatY2.data, rectMaskMatYBytes + ) setVertexBuffer(enc, flushBuffers[].positions.borrow, 0, 0) setVertexBuffer(enc, flushBuffers[].uvs.borrow, 0, 1) @@ -1972,13 +2100,21 @@ proc flush(ctx: MetalContext, maskTextureRead: int = ctx.maskTextureWrite) = setVertexBuffer(enc, flushBuffers[].rectMaskRadii.borrow, 0, 10) setVertexBuffer(enc, flushBuffers[].rectMaskMatX.borrow, 0, 11) setVertexBuffer(enc, flushBuffers[].rectMaskMatY.borrow, 0, 12) + when fastRectMaskLimit >= 2: + setVertexBuffer(enc, flushBuffers[].rectMaskParams2.borrow, 0, 13) + setVertexBuffer(enc, flushBuffers[].rectMaskRadii2.borrow, 0, 14) + setVertexBuffer(enc, flushBuffers[].rectMaskMatX2.borrow, 0, 15) + setVertexBuffer(enc, flushBuffers[].rectMaskMatY2.borrow, 0, 16) type VSUniforms = object proj: Mat4 var vsu = VSUniforms(proj: ctx.proj) setVertexBytes( - enc, addr vsu, NSUInteger(sizeof(VSUniforms)), (if useRectMaskPipeline: 13 else: 9) + enc, + addr vsu, + NSUInteger(sizeof(VSUniforms)), + (if useRectMaskPipeline: rectMaskUniformBufferIndex else: 9), ) type FSUniforms = object @@ -2061,6 +2197,11 @@ proc newContext*( result.rectMaskRadii.data = newSeq[float32](4 * maxQuads * 4) result.rectMaskMatX.data = newSeq[float32](4 * maxQuads * 4) result.rectMaskMatY.data = newSeq[float32](4 * maxQuads * 4) + when fastRectMaskLimit >= 2: + result.rectMaskParams2.data = newSeq[float32](4 * maxQuads * 4) + result.rectMaskRadii2.data = newSeq[float32](4 * maxQuads * 4) + result.rectMaskMatX2.data = newSeq[float32](4 * maxQuads * 4) + result.rectMaskMatY2.data = newSeq[float32](4 * maxQuads * 4) result.rectMaskStack = @[] # Allocate GPU buffers. @@ -2155,6 +2296,35 @@ proc newContext*( MTLResourceOptions(0), ) ) + when fastRectMaskLimit >= 2: + result.rectMaskParams2.buffer.resetRetained( + newBufferWithLength( + result.device.borrow, + NSUInteger(result.rectMaskParams2.data.len * sizeof(float32)), + MTLResourceOptions(0), + ) + ) + result.rectMaskRadii2.buffer.resetRetained( + newBufferWithLength( + result.device.borrow, + NSUInteger(result.rectMaskRadii2.data.len * sizeof(float32)), + MTLResourceOptions(0), + ) + ) + result.rectMaskMatX2.buffer.resetRetained( + newBufferWithLength( + result.device.borrow, + NSUInteger(result.rectMaskMatX2.data.len * sizeof(float32)), + MTLResourceOptions(0), + ) + ) + result.rectMaskMatY2.buffer.resetRetained( + newBufferWithLength( + result.device.borrow, + NSUInteger(result.rectMaskMatY2.data.len * sizeof(float32)), + MTLResourceOptions(0), + ) + ) # Indices are static. result.indices.data = newSeq[uint16](maxQuads * 6) diff --git a/src/figdraw/metal/metal_shaders.metal b/src/figdraw/metal/metal_shaders.metal index f280a22..36ffb0a 100644 --- a/src/figdraw/metal/metal_shaders.metal +++ b/src/figdraw/metal/metal_shaders.metal @@ -29,6 +29,12 @@ struct RectMaskVSOut { float4 rectMaskRadii; float4 rectMaskMatX; float4 rectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + float4 rectMaskParams2; + float4 rectMaskRadii2; + float4 rectMaskMatX2; + float4 rectMaskMatY2; +#endif }; struct VSUniforms { @@ -177,7 +183,16 @@ vertex RectMaskVSOut vs_rect_mask( const device float4* rectMaskRadii [[buffer(10)]], const device float4* rectMaskMatX [[buffer(11)]], const device float4* rectMaskMatY [[buffer(12)]], - constant VSUniforms& u [[buffer(13)]]) { +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + const device float4* rectMaskParams2 [[buffer(13)]], + const device float4* rectMaskRadii2 [[buffer(14)]], + const device float4* rectMaskMatX2 [[buffer(15)]], + const device float4* rectMaskMatY2 [[buffer(16)]], + constant VSUniforms& u [[buffer(17)]] +#else + constant VSUniforms& u [[buffer(13)]] +#endif + ) { RectMaskVSOut out; float2 p = positions[vid]; out.position = u.proj * float4(p.x, p.y, 0.0, 1.0); @@ -194,6 +209,12 @@ vertex RectMaskVSOut vs_rect_mask( out.rectMaskRadii = rectMaskRadii[vid]; out.rectMaskMatX = rectMaskMatX[vid]; out.rectMaskMatY = rectMaskMatY[vid]; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + out.rectMaskParams2 = rectMaskParams2[vid]; + out.rectMaskRadii2 = rectMaskRadii2[vid]; + out.rectMaskMatX2 = rectMaskMatX2[vid]; + out.rectMaskMatY2 = rectMaskMatY2[vid]; +#endif return out; } @@ -400,6 +421,16 @@ fragment float4 fs_rect_mask( in.rectMaskMatY, u.aaFactor ); +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + fragColor.w *= rectMaskAlpha( + in.pos, + in.rectMaskParams2, + in.rectMaskRadii2, + in.rectMaskMatX2, + in.rectMaskMatY2, + u.aaFactor + ); +#endif return fragColor; } diff --git a/src/figdraw/opengl/glcontext.nim b/src/figdraw/opengl/glcontext.nim index 295901f..8fe87ca 100644 --- a/src/figdraw/opengl/glcontext.nim +++ b/src/figdraw/opengl/glcontext.nim @@ -26,6 +26,9 @@ proc round*(v: Vec2): Vec2 = vec2(round(v.x), round(v.y)) const quadLimit = 10_921 +const fastRectMaskLimit = figbackend.FigDrawFastRectMaskLimit +const rectMaskShaderDefines = + "#define FIGDRAW_FAST_RECT_MASK_LIMIT " & $fastRectMaskLimit & "\n" when defined(emscripten): type SdfModeData = float32 @@ -83,12 +86,16 @@ type OpenGlContext* = ref object of figbackend.BackendContext sdfRadii: tuple[buffer: Buffer, data: seq[float32]] ## Vec4: (topRight, bottomRight, topLeft, bottomLeft) sdfModeAttr: tuple[buffer: Buffer, data: seq[SdfModeData]] ## SDFMode value - sdfFactors: tuple[buffer: Buffer, data: seq[float32]] ## Vec2: (factor, spread) - subpixelShifts: tuple[buffer: Buffer, data: seq[float32]] ## Scalar shift in px + sdfFactors: tuple[buffer: Buffer, data: seq[float32]] + ## Vec4: (factor, spread, text subpixel shift, unused) rectMaskParams: tuple[buffer: Buffer, data: seq[float32]] rectMaskRadii: tuple[buffer: Buffer, data: seq[float32]] rectMaskMatX: tuple[buffer: Buffer, data: seq[float32]] rectMaskMatY: tuple[buffer: Buffer, data: seq[float32]] + when fastRectMaskLimit >= 2: + rectMaskParams2: tuple[buffer: Buffer, data: seq[float32]] + rectMaskRadii2: tuple[buffer: Buffer, data: seq[float32]] + rectMaskMat2: tuple[buffer: Buffer, data: seq[float32]] rectMaskStack: seq[RectMask] # SDF shader uniforms (global) @@ -125,12 +132,10 @@ proc upload(ctx: OpenGlContext) = ctx.sdfRadii.buffer.count = ctx.quadCount * 4 ctx.sdfModeAttr.buffer.count = ctx.quadCount * 4 ctx.sdfFactors.buffer.count = ctx.quadCount * 4 - ctx.subpixelShifts.buffer.count = ctx.quadCount * 4 bindBufferData(ctx.sdfParams.buffer.addr, ctx.sdfParams.data[0].addr) bindBufferData(ctx.sdfRadii.buffer.addr, ctx.sdfRadii.data[0].addr) bindBufferData(ctx.sdfModeAttr.buffer.addr, ctx.sdfModeAttr.data[0].addr) bindBufferData(ctx.sdfFactors.buffer.addr, ctx.sdfFactors.data[0].addr) - bindBufferData(ctx.subpixelShifts.buffer.addr, ctx.subpixelShifts.data[0].addr) proc uploadRectMasks(ctx: OpenGlContext) = ctx.rectMaskParams.buffer.count = ctx.quadCount * 4 @@ -141,6 +146,13 @@ proc uploadRectMasks(ctx: OpenGlContext) = bindBufferData(ctx.rectMaskRadii.buffer.addr, ctx.rectMaskRadii.data[0].addr) bindBufferData(ctx.rectMaskMatX.buffer.addr, ctx.rectMaskMatX.data[0].addr) bindBufferData(ctx.rectMaskMatY.buffer.addr, ctx.rectMaskMatY.data[0].addr) + when fastRectMaskLimit >= 2: + ctx.rectMaskParams2.buffer.count = ctx.quadCount * 4 + ctx.rectMaskRadii2.buffer.count = ctx.quadCount * 4 + ctx.rectMaskMat2.buffer.count = ctx.quadCount * 4 + bindBufferData(ctx.rectMaskParams2.buffer.addr, ctx.rectMaskParams2.data[0].addr) + bindBufferData(ctx.rectMaskRadii2.buffer.addr, ctx.rectMaskRadii2.data[0].addr) + bindBufferData(ctx.rectMaskMat2.buffer.addr, ctx.rectMaskMat2.data[0].addr) proc setUpMaskFramebuffer(ctx: OpenGlContext) = glBindFramebuffer(GL_FRAMEBUFFER, ctx.maskFramebufferId) @@ -274,8 +286,9 @@ proc newContext*( newShaderStatic("glsl/emscripten/atlas.vert", "glsl/emscripten/mask.frag") result.mainShader = newShaderStatic("glsl/emscripten/atlas.vert", "glsl/emscripten/atlas.frag") - result.rectMaskShader = newShaderStatic( - "glsl/emscripten/atlas_rect_mask.vert", "glsl/emscripten/atlas_rect_mask.frag" + result.rectMaskShader = newShaderStaticWithDefines( + "glsl/emscripten/atlas_rect_mask.vert", "glsl/emscripten/atlas_rect_mask.frag", + rectMaskShaderDefines, ) result.blurShader = newShaderStatic("glsl/emscripten/blur.vert", "glsl/emscripten/blur.frag") @@ -283,8 +296,9 @@ proc newContext*( try: result.maskShader = newShaderStatic("glsl/atlas.vert", "glsl/mask.frag") result.mainShader = newShaderStatic("glsl/atlas.vert", "glsl/atlas.frag") - result.rectMaskShader = - newShaderStatic("glsl/atlas_rect_mask.vert", "glsl/atlas_rect_mask.frag") + result.rectMaskShader = newShaderStaticWithDefines( + "glsl/atlas_rect_mask.vert", "glsl/atlas_rect_mask.frag", rectMaskShaderDefines + ) result.blurShader = newShaderStatic("glsl/blur.vert", "glsl/blur.frag") except ShaderCompilationError: info "OpenGL 3.30 failed, trying GLSL ES fallback" @@ -292,8 +306,9 @@ proc newContext*( newShaderStatic("glsl/emscripten/atlas.vert", "glsl/emscripten/mask.frag") result.mainShader = newShaderStatic("glsl/emscripten/atlas.vert", "glsl/emscripten/atlas.frag") - result.rectMaskShader = newShaderStatic( - "glsl/emscripten/atlas_rect_mask.vert", "glsl/emscripten/atlas_rect_mask.frag" + result.rectMaskShader = newShaderStaticWithDefines( + "glsl/emscripten/atlas_rect_mask.vert", "glsl/emscripten/atlas_rect_mask.frag", + rectMaskShaderDefines, ) result.blurShader = newShaderStatic("glsl/emscripten/blur.vert", "glsl/emscripten/blur.frag") @@ -361,18 +376,12 @@ proc newContext*( newSeq[SdfModeData](result.sdfModeAttr.buffer.kind.componentCount() * maxQuads * 4) result.sdfFactors.buffer.componentType = cGL_FLOAT - result.sdfFactors.buffer.kind = bkVEC2 + result.sdfFactors.buffer.kind = bkVEC4 result.sdfFactors.buffer.target = GL_ARRAY_BUFFER result.sdfFactors.buffer.usage = GL_STREAM_DRAW result.sdfFactors.data = newSeq[float32](result.sdfFactors.buffer.kind.componentCount() * maxQuads * 4) - result.subpixelShifts.buffer.componentType = cGL_FLOAT - result.subpixelShifts.buffer.kind = bkSCALAR - result.subpixelShifts.buffer.target = GL_ARRAY_BUFFER - result.subpixelShifts.buffer.usage = GL_STREAM_DRAW - result.subpixelShifts.data = newSeq[float32](maxQuads * 4) - result.rectMaskParams.buffer.componentType = cGL_FLOAT result.rectMaskParams.buffer.kind = bkVEC4 result.rectMaskParams.buffer.target = GL_ARRAY_BUFFER @@ -401,6 +410,29 @@ proc newContext*( result.rectMaskMatY.data = newSeq[float32](result.rectMaskMatY.buffer.kind.componentCount() * maxQuads * 4) + when fastRectMaskLimit >= 2: + result.rectMaskParams2.buffer.componentType = cGL_FLOAT + result.rectMaskParams2.buffer.kind = bkVEC4 + result.rectMaskParams2.buffer.target = GL_ARRAY_BUFFER + result.rectMaskParams2.buffer.usage = GL_STREAM_DRAW + result.rectMaskParams2.data = newSeq[float32]( + result.rectMaskParams2.buffer.kind.componentCount() * maxQuads * 4 + ) + + result.rectMaskRadii2.buffer.componentType = cGL_FLOAT + result.rectMaskRadii2.buffer.kind = bkVEC4 + result.rectMaskRadii2.buffer.target = GL_ARRAY_BUFFER + result.rectMaskRadii2.buffer.usage = GL_STREAM_DRAW + result.rectMaskRadii2.data = + newSeq[float32](result.rectMaskRadii2.buffer.kind.componentCount() * maxQuads * 4) + + result.rectMaskMat2.buffer.componentType = cGL_FLOAT + result.rectMaskMat2.buffer.kind = bkVEC4 + result.rectMaskMat2.buffer.target = GL_ARRAY_BUFFER + result.rectMaskMat2.buffer.usage = GL_STREAM_DRAW + result.rectMaskMat2.data = + newSeq[float32](result.rectMaskMat2.buffer.kind.componentCount() * maxQuads * 4) + result.indices.buffer.componentType = GL_UNSIGNED_SHORT result.indices.buffer.kind = bkSCALAR result.indices.buffer.target = GL_ELEMENT_ARRAY_BUFFER @@ -440,7 +472,6 @@ proc newContext*( result.mainShader.bindAttrib("vertexSdfRadii", result.sdfRadii.buffer) result.mainShader.bindAttrib("vertexSdfMode", result.sdfModeAttr.buffer) result.mainShader.bindAttrib("vertexSdfFactors", result.sdfFactors.buffer) - result.mainShader.bindAttrib("vertexSubpixelShift", result.subpixelShifts.buffer) # Main shader with per-vertex fast rect masks. glGenVertexArrays(1, result.rectMaskVertexArrayId.addr) @@ -454,11 +485,18 @@ proc newContext*( result.rectMaskShader.bindAttrib("vertexSdfRadii", result.sdfRadii.buffer) result.rectMaskShader.bindAttrib("vertexSdfMode", result.sdfModeAttr.buffer) result.rectMaskShader.bindAttrib("vertexSdfFactors", result.sdfFactors.buffer) - result.rectMaskShader.bindAttrib("vertexSubpixelShift", result.subpixelShifts.buffer) result.rectMaskShader.bindAttrib("vertexRectMaskParams", result.rectMaskParams.buffer) result.rectMaskShader.bindAttrib("vertexRectMaskRadii", result.rectMaskRadii.buffer) result.rectMaskShader.bindAttrib("vertexRectMaskMatX", result.rectMaskMatX.buffer) result.rectMaskShader.bindAttrib("vertexRectMaskMatY", result.rectMaskMatY.buffer) + when fastRectMaskLimit >= 2: + result.rectMaskShader.bindAttrib( + "vertexRectMaskParams2", result.rectMaskParams2.buffer + ) + result.rectMaskShader.bindAttrib( + "vertexRectMaskRadii2", result.rectMaskRadii2.buffer + ) + result.rectMaskShader.bindAttrib("vertexRectMaskMat2", result.rectMaskMat2.buffer) # Mask shader. glGenVertexArrays(1, result.maskVertexArrayId.addr) @@ -469,7 +507,6 @@ proc newContext*( result.maskShader.bindAttrib("vertexSdfParams", result.sdfParams.buffer) result.maskShader.bindAttrib("vertexSdfRadii", result.sdfRadii.buffer) result.maskShader.bindAttrib("vertexSdfMode", result.sdfModeAttr.buffer) - result.maskShader.bindAttrib("vertexSubpixelShift", result.subpixelShifts.buffer) # Fullscreen triangle buffers for blur passes. result.blurPositions.componentType = cGL_FLOAT @@ -703,9 +740,6 @@ proc setVert2(buf: var seq[float32], i: int, v: Vec2) = buf[i * 2 + 0] = v.x buf[i * 2 + 1] = v.y -proc setVert1(buf: var seq[float32], i: int, v: float32) = - buf[i] = v - proc setVert4(buf: var seq[float32], i: int, v: Vec4) = buf[i * 4 + 0] = v.x buf[i * 4 + 1] = v.y @@ -750,12 +784,12 @@ proc activeSubpixelShift(ctx: OpenGlContext): float32 = return 0.0'f32 max(0.0'f32, min(ctx.textSubpixelShift, 0.999'f32)) -proc setQuadSubpixelShift(ctx: OpenGlContext, offset: int) = - let shift = ctx.activeSubpixelShift() - ctx.subpixelShifts.data.setVert1(offset + 0, shift) - ctx.subpixelShifts.data.setVert1(offset + 1, shift) - ctx.subpixelShifts.data.setVert1(offset + 2, shift) - ctx.subpixelShifts.data.setVert1(offset + 3, shift) +proc setQuadSdfFactors(ctx: OpenGlContext, offset: int, factors: Vec2) = + let packed = vec4(factors.x, factors.y, ctx.activeSubpixelShift(), 0.0'f32) + ctx.sdfFactors.data.setVert4(offset + 0, packed) + ctx.sdfFactors.data.setVert4(offset + 1, packed) + ctx.sdfFactors.data.setVert4(offset + 2, packed) + ctx.sdfFactors.data.setVert4(offset + 3, packed) proc makeRectMask( ctx: OpenGlContext, maskRect: Rect, radii: array[DirectionCorners, float32] @@ -781,6 +815,13 @@ proc setRectMaskVert4( ctx.rectMaskMatX.data.setVert4(offset + i, matX) ctx.rectMaskMatY.data.setVert4(offset + i, matY) +when fastRectMaskLimit >= 2: + proc setRectMask2Vert4(ctx: OpenGlContext, offset: int, params, radii, mat2: Vec4) = + for i in 0 ..< 4: + ctx.rectMaskParams2.data.setVert4(offset + i, params) + ctx.rectMaskRadii2.data.setVert4(offset + i, radii) + ctx.rectMaskMat2.data.setVert4(offset + i, mat2) + proc setDisabledRectMaskVerts(ctx: OpenGlContext, firstVertex, vertexCount: int) = let params = vec4(0.0'f32, 0.0'f32, -1.0'f32, -1.0'f32) @@ -790,40 +831,77 @@ proc setDisabledRectMaskVerts(ctx: OpenGlContext, firstVertex, vertexCount: int) ctx.rectMaskRadii.data.setVert4(i, zero4) ctx.rectMaskMatX.data.setVert4(i, zero4) ctx.rectMaskMatY.data.setVert4(i, zero4) + when fastRectMaskLimit >= 2: + ctx.rectMaskParams2.data.setVert4(i, params) + ctx.rectMaskRadii2.data.setVert4(i, zero4) + ctx.rectMaskMat2.data.setVert4(i, zero4) + +proc fastRectMaskCount(ctx: OpenGlContext): int = + for rectMask in ctx.rectMaskStack: + if rectMask.kind == rmkFast: + inc result proc setRectMaskVert4(ctx: OpenGlContext, offset: int) = - if ctx.maskBegun: + when fastRectMaskLimit == 0: return - - var - hasRectMask = false - params = vec4(0.0'f32) - radii = vec4(0.0'f32) - matX = vec4(0.0'f32) - matY = vec4(0.0'f32) - - if ctx.rectMaskStack.len > 0: - for i in countdown(ctx.rectMaskStack.len - 1, 0): - let rectMask = ctx.rectMaskStack[i] - if rectMask.kind == rmkFast: - hasRectMask = true - params = rectMask.params - radii = rectMask.radii - matX = rectMask.matX - matY = rectMask.matY - break - - if hasRectMask: - if not ctx.batchHasRectMask: - ctx.setDisabledRectMaskVerts(0, offset) - ctx.batchHasRectMask = true - ctx.setRectMaskVert4(offset, params, radii, matX, matY) - elif ctx.batchHasRectMask: - ctx.setDisabledRectMaskVerts(offset, 4) + else: + if ctx.maskBegun: + return + + var + maskCount = 0 + params = vec4(0.0'f32) + radii = vec4(0.0'f32) + matX = vec4(0.0'f32) + matY = vec4(0.0'f32) + when fastRectMaskLimit >= 2: + var + params2 = vec4(0.0'f32) + radii2 = vec4(0.0'f32) + mat2 = vec4(0.0'f32) + + if ctx.rectMaskStack.len > 0: + for i in countdown(ctx.rectMaskStack.len - 1, 0): + let rectMask = ctx.rectMaskStack[i] + if rectMask.kind == rmkFast: + if maskCount == 0: + params = rectMask.params + radii = rectMask.radii + matX = rectMask.matX + matY = rectMask.matY + inc maskCount + elif fastRectMaskLimit >= 2 and maskCount == 1: + when fastRectMaskLimit >= 2: + params2 = rectMask.params + radii2 = rectMask.radii + mat2 = + vec4(rectMask.matX.x, rectMask.matX.y, rectMask.matX.z, rectMask.matY.x) + matX.w = rectMask.matY.y + matY.w = rectMask.matY.z + inc maskCount + if maskCount >= fastRectMaskLimit: + break + + if maskCount > 0: + if not ctx.batchHasRectMask: + ctx.setDisabledRectMaskVerts(0, offset) + ctx.batchHasRectMask = true + ctx.setRectMaskVert4(offset, params, radii, matX, matY) + when fastRectMaskLimit >= 2: + if maskCount >= 2: + ctx.setRectMask2Vert4(offset, params2, radii2, mat2) + else: + let + disabledParams = vec4(0.0'f32, 0.0'f32, -1.0'f32, -1.0'f32) + zero4 = vec4(0.0'f32) + ctx.setRectMask2Vert4(offset, disabledParams, zero4, zero4) + elif ctx.batchHasRectMask: + ctx.setDisabledRectMaskVerts(offset, 4) template setRectMaskVert4IfNeeded(ctx: OpenGlContext, offset: int) = - if not ctx.maskBegun and (ctx.batchHasRectMask or ctx.rectMaskStack.len > 0): - ctx.setRectMaskVert4(offset) + when fastRectMaskLimit > 0: + if not ctx.maskBegun and (ctx.batchHasRectMask or ctx.rectMaskStack.len > 0): + ctx.setRectMaskVert4(offset) func `*`*(m: Mat4, v: Vec2): Vec2 = (m * vec3(v.x, v.y, 0.0)).xy @@ -864,10 +942,7 @@ proc drawQuad*( ctx.sdfRadii.data.setVert4(offset + 3, zero4) let defaultFactors = vec2(0.0'f32, 0.0'f32) - ctx.sdfFactors.data.setVert2(offset + 0, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 1, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 2, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 3, defaultFactors) + ctx.setQuadSdfFactors(offset, defaultFactors) # atlas fragment mode when defined(emscripten): @@ -878,7 +953,6 @@ proc drawQuad*( ctx.sdfModeAttr.data[offset + 1] = modeVal ctx.sdfModeAttr.data[offset + 2] = modeVal ctx.sdfModeAttr.data[offset + 3] = modeVal - ctx.setQuadSubpixelShift(offset) ctx.setRectMaskVert4IfNeeded(offset) inc ctx.quadCount @@ -971,10 +1045,7 @@ proc drawUvRectAtlasSdf( ctx.sdfRadii.data.setVert4(offset + 2, zero4) ctx.sdfRadii.data.setVert4(offset + 3, zero4) - ctx.sdfFactors.data.setVert2(offset + 0, factors) - ctx.sdfFactors.data.setVert2(offset + 1, factors) - ctx.sdfFactors.data.setVert2(offset + 2, factors) - ctx.sdfFactors.data.setVert2(offset + 3, factors) + ctx.setQuadSdfFactors(offset, factors) when defined(emscripten): let modeVal = mode.int.float32 @@ -984,7 +1055,6 @@ proc drawUvRectAtlasSdf( ctx.sdfModeAttr.data[offset + 1] = modeVal ctx.sdfModeAttr.data[offset + 2] = modeVal ctx.sdfModeAttr.data[offset + 3] = modeVal - ctx.setQuadSubpixelShift(offset) ctx.setRectMaskVert4IfNeeded(offset) inc ctx.quadCount @@ -1105,10 +1175,7 @@ proc drawUvRect(ctx: OpenGlContext, at, to: Vec2, uvAt, uvTo: Vec2, color: Color ctx.sdfRadii.data.setVert4(offset + 3, zero4) let defaultFactors = vec2(0.0'f32, 0.0'f32) - ctx.sdfFactors.data.setVert2(offset + 0, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 1, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 2, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 3, defaultFactors) + ctx.setQuadSdfFactors(offset, defaultFactors) when defined(emscripten): let modeVal = 0.0'f32 @@ -1118,7 +1185,6 @@ proc drawUvRect(ctx: OpenGlContext, at, to: Vec2, uvAt, uvTo: Vec2, color: Color ctx.sdfModeAttr.data[offset + 1] = modeVal ctx.sdfModeAttr.data[offset + 2] = modeVal ctx.sdfModeAttr.data[offset + 3] = modeVal - ctx.setQuadSubpixelShift(offset) ctx.setRectMaskVert4IfNeeded(offset) inc ctx.quadCount @@ -1173,10 +1239,7 @@ proc drawUvRect( ctx.sdfRadii.data.setVert4(offset + 3, zero4) let defaultFactors = vec2(0.0'f32, 0.0'f32) - ctx.sdfFactors.data.setVert2(offset + 0, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 1, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 2, defaultFactors) - ctx.sdfFactors.data.setVert2(offset + 3, defaultFactors) + ctx.setQuadSdfFactors(offset, defaultFactors) when defined(emscripten): let modeVal = 0.0'f32 @@ -1186,7 +1249,6 @@ proc drawUvRect( ctx.sdfModeAttr.data[offset + 1] = modeVal ctx.sdfModeAttr.data[offset + 2] = modeVal ctx.sdfModeAttr.data[offset + 3] = modeVal - ctx.setQuadSubpixelShift(offset) ctx.setRectMaskVert4IfNeeded(offset) inc ctx.quadCount @@ -1429,10 +1491,7 @@ proc drawRoundedRectSdfOpenGl( vec2(factor, spread) else: vec2(factor, clamp(fillMidPos, 0.01'f32, 0.99'f32)) - ctx.sdfFactors.data.setVert2(offset + 0, factors) - ctx.sdfFactors.data.setVert2(offset + 1, factors) - ctx.sdfFactors.data.setVert2(offset + 2, factors) - ctx.sdfFactors.data.setVert2(offset + 3, factors) + ctx.setQuadSdfFactors(offset, factors) when defined(emscripten): let modeVal = encodeSdfMode(mode, fillMode) @@ -1442,7 +1501,6 @@ proc drawRoundedRectSdfOpenGl( ctx.sdfModeAttr.data[offset + 1] = modeVal ctx.sdfModeAttr.data[offset + 2] = modeVal ctx.sdfModeAttr.data[offset + 3] = modeVal - ctx.setQuadSubpixelShift(offset) ctx.setRectMaskVert4IfNeeded(offset) inc ctx.quadCount @@ -1703,7 +1761,8 @@ method beginRectMask*( assert ctx.frameBegun == true, "ctx.beginFrame has not been called." assert ctx.maskBegun == false, "ctx.beginRectMask cannot start inside a mask." - if ctx.rectMaskStack.len == 0 and maskRect.w > 0.0'f32 and maskRect.h > 0.0'f32: + if fastRectMaskLimit > 0 and ctx.fastRectMaskCount() < fastRectMaskLimit and + maskRect.w > 0.0'f32 and maskRect.h > 0.0'f32: ctx.rectMaskStack.add(ctx.makeRectMask(maskRect, radii)) else: ctx.beginMask(maskRect, radii) diff --git a/src/figdraw/opengl/glsl/atlas.frag b/src/figdraw/opengl/glsl/atlas.frag index b678c61..69a3446 100644 --- a/src/figdraw/opengl/glsl/atlas.frag +++ b/src/figdraw/opengl/glsl/atlas.frag @@ -8,8 +8,7 @@ in vec4 fillStopColor; in vec4 sdfParams; in vec4 sdfRadii; in float sdfMode; -in vec2 sdfFactors; -in float subpixelShift; +in vec4 sdfFactors; uniform vec2 windowFrame; uniform sampler2D atlasTex; @@ -130,7 +129,7 @@ void main() { if (sdfModeInt == sdfModeAtlas) { vec2 atlasUv = uv; if (subpixelPositioningEnabled) { - atlasUv.x -= subpixelShift * atlasTexelSize.x; + atlasUv.x -= sdfFactors.z * atlasTexelSize.x; } vec4 tex = texture(atlasTex, atlasUv); fragColor = vec4( diff --git a/src/figdraw/opengl/glsl/atlas.vert b/src/figdraw/opengl/glsl/atlas.vert index f80cf7c..90a747c 100644 --- a/src/figdraw/opengl/glsl/atlas.vert +++ b/src/figdraw/opengl/glsl/atlas.vert @@ -8,8 +8,7 @@ in vec4 vertexFillStopColor; in vec4 vertexSdfParams; in vec4 vertexSdfRadii; in float vertexSdfMode; -in vec2 vertexSdfFactors; -in float vertexSubpixelShift; +in vec4 vertexSdfFactors; uniform mat4 proj; @@ -21,8 +20,7 @@ out vec4 fillStopColor; out vec4 sdfParams; out vec4 sdfRadii; out float sdfMode; -out vec2 sdfFactors; -out float subpixelShift; +out vec4 sdfFactors; void main() { pos = vertexPos; @@ -34,6 +32,5 @@ void main() { sdfRadii = vertexSdfRadii; sdfMode = vertexSdfMode; sdfFactors = vertexSdfFactors; - subpixelShift = vertexSubpixelShift; gl_Position = proj * vec4(vertexPos.x, vertexPos.y, 0.0, 1.0); } diff --git a/src/figdraw/opengl/glsl/atlas_rect_mask.frag b/src/figdraw/opengl/glsl/atlas_rect_mask.frag index dee0464..aa94cd1 100644 --- a/src/figdraw/opengl/glsl/atlas_rect_mask.frag +++ b/src/figdraw/opengl/glsl/atlas_rect_mask.frag @@ -8,12 +8,16 @@ in vec4 fillStopColor; in vec4 sdfParams; in vec4 sdfRadii; in float sdfMode; -in vec2 sdfFactors; -in float subpixelShift; +in vec4 sdfFactors; in vec4 rectMaskParams; in vec4 rectMaskRadii; in vec4 rectMaskMatX; in vec4 rectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +in vec4 rectMaskParams2; +in vec4 rectMaskRadii2; +in vec4 rectMaskMat2; +#endif uniform vec2 windowFrame; uniform sampler2D atlasTex; @@ -76,20 +80,35 @@ float shadowProfile(float sd, float blurRadius) { return exp(-0.5 * z * z); } -float rectMaskAlpha(vec2 pixelPos) { - if (rectMaskParams.z < 0.0 || rectMaskParams.w < 0.0) { +float rectMaskAlphaOne( + vec2 pixelPos, + vec4 params, + vec4 radii, + vec4 matX, + vec4 matY) { + if (params.z < 0.0 || params.w < 0.0) { return 1.0; } vec2 local = vec2( - dot(rectMaskMatX.xy, pixelPos) + rectMaskMatX.z, - dot(rectMaskMatY.xy, pixelPos) + rectMaskMatY.z + dot(matX.xy, pixelPos) + matX.z, + dot(matY.xy, pixelPos) + matY.z ); - vec2 q = local - rectMaskParams.xy; - float dist = sdRoundedBox(vec2(q.x, -q.y), rectMaskParams.zw, rectMaskRadii); + vec2 q = local - params.xy; + float dist = sdRoundedBox(vec2(q.x, -q.y), params.zw, radii); return 1.0 - clamp(aaFactor * dist + 0.5, 0.0, 1.0); } +float rectMaskAlpha(vec2 pixelPos) { + float alpha = + rectMaskAlphaOne(pixelPos, rectMaskParams, rectMaskRadii, rectMaskMatX, rectMaskMatY); +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + vec4 matY2 = vec4(rectMaskMat2.w, rectMaskMatX.w, rectMaskMatY.w, 0.0); + alpha *= rectMaskAlphaOne(pixelPos, rectMaskParams2, rectMaskRadii2, rectMaskMat2, matY2); +#endif + return alpha; +} + float linear3T(int fillMode, vec2 uv) { switch (fillMode) { case 1: @@ -148,7 +167,7 @@ void main() { if (sdfModeInt == sdfModeAtlas) { vec2 atlasUv = uv; if (subpixelPositioningEnabled) { - atlasUv.x -= subpixelShift * atlasTexelSize.x; + atlasUv.x -= sdfFactors.z * atlasTexelSize.x; } vec4 tex = texture(atlasTex, atlasUv); fragColor = vec4( diff --git a/src/figdraw/opengl/glsl/atlas_rect_mask.vert b/src/figdraw/opengl/glsl/atlas_rect_mask.vert index 7921641..3310ad1 100644 --- a/src/figdraw/opengl/glsl/atlas_rect_mask.vert +++ b/src/figdraw/opengl/glsl/atlas_rect_mask.vert @@ -8,12 +8,16 @@ in vec4 vertexFillStopColor; in vec4 vertexSdfParams; in vec4 vertexSdfRadii; in float vertexSdfMode; -in vec2 vertexSdfFactors; -in float vertexSubpixelShift; +in vec4 vertexSdfFactors; in vec4 vertexRectMaskParams; in vec4 vertexRectMaskRadii; in vec4 vertexRectMaskMatX; in vec4 vertexRectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +in vec4 vertexRectMaskParams2; +in vec4 vertexRectMaskRadii2; +in vec4 vertexRectMaskMat2; +#endif uniform mat4 proj; @@ -25,12 +29,16 @@ out vec4 fillStopColor; out vec4 sdfParams; out vec4 sdfRadii; out float sdfMode; -out vec2 sdfFactors; -out float subpixelShift; +out vec4 sdfFactors; out vec4 rectMaskParams; out vec4 rectMaskRadii; out vec4 rectMaskMatX; out vec4 rectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +out vec4 rectMaskParams2; +out vec4 rectMaskRadii2; +out vec4 rectMaskMat2; +#endif void main() { pos = vertexPos; @@ -42,10 +50,14 @@ void main() { sdfRadii = vertexSdfRadii; sdfMode = vertexSdfMode; sdfFactors = vertexSdfFactors; - subpixelShift = vertexSubpixelShift; rectMaskParams = vertexRectMaskParams; rectMaskRadii = vertexRectMaskRadii; rectMaskMatX = vertexRectMaskMatX; rectMaskMatY = vertexRectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + rectMaskParams2 = vertexRectMaskParams2; + rectMaskRadii2 = vertexRectMaskRadii2; + rectMaskMat2 = vertexRectMaskMat2; +#endif gl_Position = proj * vec4(vertexPos.x, vertexPos.y, 0.0, 1.0); } diff --git a/src/figdraw/opengl/glsl/emscripten/atlas.frag b/src/figdraw/opengl/glsl/emscripten/atlas.frag index c5c6ba1..941bf06 100644 --- a/src/figdraw/opengl/glsl/emscripten/atlas.frag +++ b/src/figdraw/opengl/glsl/emscripten/atlas.frag @@ -11,8 +11,7 @@ varying vec4 fillStopColor; varying vec4 sdfParams; varying vec4 sdfRadii; varying float sdfMode; -varying vec2 sdfFactors; -varying float subpixelShift; +varying vec4 sdfFactors; uniform vec2 windowFrame; uniform sampler2D atlasTex; @@ -132,7 +131,7 @@ void main() { if (sdfModeInt == sdfModeAtlas) { vec2 atlasUv = uv; if (subpixelPositioningEnabled) { - atlasUv.x -= subpixelShift * atlasTexelSize.x; + atlasUv.x -= sdfFactors.z * atlasTexelSize.x; } vec4 tex = texture2D(atlasTex, atlasUv); fragColor = vec4( diff --git a/src/figdraw/opengl/glsl/emscripten/atlas.vert b/src/figdraw/opengl/glsl/emscripten/atlas.vert index 6406619..f032bcb 100644 --- a/src/figdraw/opengl/glsl/emscripten/atlas.vert +++ b/src/figdraw/opengl/glsl/emscripten/atlas.vert @@ -10,8 +10,7 @@ attribute vec4 vertexFillStopColor; attribute vec4 vertexSdfParams; attribute vec4 vertexSdfRadii; attribute float vertexSdfMode; -attribute vec2 vertexSdfFactors; -attribute float vertexSubpixelShift; +attribute vec4 vertexSdfFactors; uniform mat4 proj; @@ -23,8 +22,7 @@ varying vec4 fillStopColor; varying vec4 sdfParams; varying vec4 sdfRadii; varying float sdfMode; -varying vec2 sdfFactors; -varying float subpixelShift; +varying vec4 sdfFactors; void main() { pos = vertexPos; @@ -36,6 +34,5 @@ void main() { sdfRadii = vertexSdfRadii; sdfMode = vertexSdfMode; sdfFactors = vertexSdfFactors; - subpixelShift = vertexSubpixelShift; gl_Position = proj * vec4(vertexPos.x, vertexPos.y, 0.0, 1.0); } diff --git a/src/figdraw/opengl/glsl/emscripten/atlas_rect_mask.frag b/src/figdraw/opengl/glsl/emscripten/atlas_rect_mask.frag index 488bfe7..643fde8 100644 --- a/src/figdraw/opengl/glsl/emscripten/atlas_rect_mask.frag +++ b/src/figdraw/opengl/glsl/emscripten/atlas_rect_mask.frag @@ -11,12 +11,16 @@ varying vec4 fillStopColor; varying vec4 sdfParams; varying vec4 sdfRadii; varying float sdfMode; -varying vec2 sdfFactors; -varying float subpixelShift; +varying vec4 sdfFactors; varying vec4 rectMaskParams; varying vec4 rectMaskRadii; varying vec4 rectMaskMatX; varying vec4 rectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +varying vec4 rectMaskParams2; +varying vec4 rectMaskRadii2; +varying vec4 rectMaskMat2; +#endif uniform vec2 windowFrame; uniform sampler2D atlasTex; @@ -78,20 +82,35 @@ float shadowProfile(float sd, float blurRadius) { return exp(-0.5 * z * z); } -float rectMaskAlpha(vec2 pixelPos) { - if (rectMaskParams.z < 0.0 || rectMaskParams.w < 0.0) { +float rectMaskAlphaOne( + vec2 pixelPos, + vec4 params, + vec4 radii, + vec4 matX, + vec4 matY) { + if (params.z < 0.0 || params.w < 0.0) { return 1.0; } vec2 local = vec2( - dot(rectMaskMatX.xy, pixelPos) + rectMaskMatX.z, - dot(rectMaskMatY.xy, pixelPos) + rectMaskMatY.z + dot(matX.xy, pixelPos) + matX.z, + dot(matY.xy, pixelPos) + matY.z ); - vec2 q = local - rectMaskParams.xy; - float dist = sdRoundedBox(vec2(q.x, -q.y), rectMaskParams.zw, rectMaskRadii); + vec2 q = local - params.xy; + float dist = sdRoundedBox(vec2(q.x, -q.y), params.zw, radii); return 1.0 - clamp(aaFactor * dist + 0.5, 0.0, 1.0); } +float rectMaskAlpha(vec2 pixelPos) { + float alpha = + rectMaskAlphaOne(pixelPos, rectMaskParams, rectMaskRadii, rectMaskMatX, rectMaskMatY); +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + vec4 matY2 = vec4(rectMaskMat2.w, rectMaskMatX.w, rectMaskMatY.w, 0.0); + alpha *= rectMaskAlphaOne(pixelPos, rectMaskParams2, rectMaskRadii2, rectMaskMat2, matY2); +#endif + return alpha; +} + float linear3T(int fillMode, vec2 uv) { if (fillMode == 1) { return uv.x; @@ -150,7 +169,7 @@ void main() { if (sdfModeInt == sdfModeAtlas) { vec2 atlasUv = uv; if (subpixelPositioningEnabled) { - atlasUv.x -= subpixelShift * atlasTexelSize.x; + atlasUv.x -= sdfFactors.z * atlasTexelSize.x; } vec4 tex = texture2D(atlasTex, atlasUv); fragColor = vec4( diff --git a/src/figdraw/opengl/glsl/emscripten/atlas_rect_mask.vert b/src/figdraw/opengl/glsl/emscripten/atlas_rect_mask.vert index 7ffdb6f..f4636fd 100644 --- a/src/figdraw/opengl/glsl/emscripten/atlas_rect_mask.vert +++ b/src/figdraw/opengl/glsl/emscripten/atlas_rect_mask.vert @@ -10,12 +10,16 @@ attribute vec4 vertexFillStopColor; attribute vec4 vertexSdfParams; attribute vec4 vertexSdfRadii; attribute float vertexSdfMode; -attribute vec2 vertexSdfFactors; -attribute float vertexSubpixelShift; +attribute vec4 vertexSdfFactors; attribute vec4 vertexRectMaskParams; attribute vec4 vertexRectMaskRadii; attribute vec4 vertexRectMaskMatX; attribute vec4 vertexRectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +attribute vec4 vertexRectMaskParams2; +attribute vec4 vertexRectMaskRadii2; +attribute vec4 vertexRectMaskMat2; +#endif uniform mat4 proj; @@ -27,12 +31,16 @@ varying vec4 fillStopColor; varying vec4 sdfParams; varying vec4 sdfRadii; varying float sdfMode; -varying vec2 sdfFactors; -varying float subpixelShift; +varying vec4 sdfFactors; varying vec4 rectMaskParams; varying vec4 rectMaskRadii; varying vec4 rectMaskMatX; varying vec4 rectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +varying vec4 rectMaskParams2; +varying vec4 rectMaskRadii2; +varying vec4 rectMaskMat2; +#endif void main() { pos = vertexPos; @@ -44,10 +52,14 @@ void main() { sdfRadii = vertexSdfRadii; sdfMode = vertexSdfMode; sdfFactors = vertexSdfFactors; - subpixelShift = vertexSubpixelShift; rectMaskParams = vertexRectMaskParams; rectMaskRadii = vertexRectMaskRadii; rectMaskMatX = vertexRectMaskMatX; rectMaskMatY = vertexRectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + rectMaskParams2 = vertexRectMaskParams2; + rectMaskRadii2 = vertexRectMaskRadii2; + rectMaskMat2 = vertexRectMaskMat2; +#endif gl_Position = proj * vec4(vertexPos.x, vertexPos.y, 0.0, 1.0); } diff --git a/src/figdraw/opengl/glsl/emscripten/mask.frag b/src/figdraw/opengl/glsl/emscripten/mask.frag index 4d94270..befcca0 100644 --- a/src/figdraw/opengl/glsl/emscripten/mask.frag +++ b/src/figdraw/opengl/glsl/emscripten/mask.frag @@ -8,7 +8,6 @@ varying vec4 color; varying vec4 sdfParams; varying vec4 sdfRadii; varying float sdfMode; -varying float subpixelShift; uniform vec2 windowFrame; uniform sampler2D atlasTex; diff --git a/src/figdraw/opengl/glsl/mask.frag b/src/figdraw/opengl/glsl/mask.frag index 674ebb2..0a30f45 100644 --- a/src/figdraw/opengl/glsl/mask.frag +++ b/src/figdraw/opengl/glsl/mask.frag @@ -6,7 +6,6 @@ in vec4 color; in vec4 sdfParams; in vec4 sdfRadii; in float sdfMode; -in float subpixelShift; uniform vec2 windowFrame; uniform sampler2D atlasTex; diff --git a/src/figdraw/opengl/shaders.nim b/src/figdraw/opengl/shaders.nim index 7318319..98d5c8a 100644 --- a/src/figdraw/opengl/shaders.nim +++ b/src/figdraw/opengl/shaders.nim @@ -225,6 +225,17 @@ proc newShader*(vertPath, fragPath: string): Shader = fragPathFull = dir / fragPath newShader((vertPathFull, vertCode), (fragPathFull, fragCode)) +func injectGlslDefines(source, defines: string): string = + if defines.len == 0: + return source + + if source.startsWith("#version"): + let lineEnd = source.find('\n') + if lineEnd >= 0: + return source[0 .. lineEnd] & defines & source[lineEnd + 1 ..^ 1] + + defines & source + template newShaderStatic*(vertPath, fragPath: string): Shader = ## Creates a new shader but also statically reads vertPath and fragPath ## so they are compiled into the binary. @@ -236,6 +247,22 @@ template newShaderStatic*(vertPath, fragPath: string): Shader = fragPathFull = dir / fragPath newShader((vertPathFull, vertCode), (fragPathFull, fragCode)) +template newShaderStaticWithDefines*( + vertPath, fragPath: string, defines: static[string] +): Shader = + ## Creates a new shader from static sources with GLSL defines inserted after + ## the #version line. + const + vertCode = staticRead(vertPath) + fragCode = staticRead(fragPath) + dir = getProjectPath() + vertPathFull = dir / vertPath + fragPathFull = dir / fragPath + newShader( + (vertPathFull, injectGlslDefines(vertCode, defines)), + (fragPathFull, injectGlslDefines(fragCode, defines)), + ) + proc hasUniform*(shader: Shader, name: string): bool = for uniform in shader.uniforms: if uniform.name == name: @@ -413,8 +440,7 @@ proc bindAttrib*(shader: Shader, name: string, buffer: Buffer) = # integer data. Other scalar formats (e.g. u16 enum values) should flow # through the float attribute path for GLSL ES compatibility. let useIntegerPointer = - not buffer.normalized and - buffer.kind == bkSCALAR and + not buffer.normalized and buffer.kind == bkSCALAR and (buffer.componentType == cGL_INT or buffer.componentType == GL_UNSIGNED_INT) if not useIntegerPointer: diff --git a/src/figdraw/vulkan/shaders/sdf.frag.glsl b/src/figdraw/vulkan/shaders/sdf.frag.glsl index a4ce2c4..dcbc001 100644 --- a/src/figdraw/vulkan/shaders/sdf.frag.glsl +++ b/src/figdraw/vulkan/shaders/sdf.frag.glsl @@ -23,6 +23,12 @@ layout(location = 9) in vec4 vRectMaskParams; layout(location = 10) in vec4 vRectMaskRadii; layout(location = 11) in vec4 vRectMaskMatX; layout(location = 12) in vec4 vRectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +layout(location = 13) in vec4 vRectMaskParams2; +layout(location = 14) in vec4 vRectMaskRadii2; +layout(location = 15) in vec4 vRectMaskMatX2; +layout(location = 16) in vec4 vRectMaskMatY2; +#endif layout(location = 0) out vec4 fragColor; @@ -61,20 +67,36 @@ float sdRoundedBox(vec2 p, vec2 b, vec4 r) { return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; } -float rectMaskAlpha(vec2 pixelPos) { - if (vRectMaskParams.z <= 0.0 || vRectMaskParams.w <= 0.0) { +float rectMaskAlphaOne( + vec2 pixelPos, + vec4 params, + vec4 radii, + vec4 matX, + vec4 matY) { + if (params.z <= 0.0 || params.w <= 0.0) { return 1.0; } vec2 local = vec2( - dot(vRectMaskMatX.xy, pixelPos) + vRectMaskMatX.z, - dot(vRectMaskMatY.xy, pixelPos) + vRectMaskMatY.z + dot(matX.xy, pixelPos) + matX.z, + dot(matY.xy, pixelPos) + matY.z ); - vec2 q = local - vRectMaskParams.xy; - float dist = sdRoundedBox(vec2(q.x, -q.y), vRectMaskParams.zw, vRectMaskRadii); + vec2 q = local - params.xy; + float dist = sdRoundedBox(vec2(q.x, -q.y), params.zw, radii); return 1.0 - clamp(uFS.aaFactor * dist + 0.5, 0.0, 1.0); } +float rectMaskAlpha(vec2 pixelPos) { + float alpha = + rectMaskAlphaOne(pixelPos, vRectMaskParams, vRectMaskRadii, vRectMaskMatX, vRectMaskMatY); +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + alpha *= rectMaskAlphaOne( + pixelPos, vRectMaskParams2, vRectMaskRadii2, vRectMaskMatX2, vRectMaskMatY2 + ); +#endif + return alpha; +} + float shadowProfile(float sd, float blurRadius) { // CSS-like calibration: sigma ~= blurRadius / 2 float sigma = max(0.5 * blurRadius, 0.5); diff --git a/src/figdraw/vulkan/shaders/sdf.frag.spv b/src/figdraw/vulkan/shaders/sdf.frag.spv index a00c737..6c1c2c8 100644 Binary files a/src/figdraw/vulkan/shaders/sdf.frag.spv and b/src/figdraw/vulkan/shaders/sdf.frag.spv differ diff --git a/src/figdraw/vulkan/shaders/sdf.rectmask0.frag.spv b/src/figdraw/vulkan/shaders/sdf.rectmask0.frag.spv new file mode 100644 index 0000000..c6599d9 Binary files /dev/null and b/src/figdraw/vulkan/shaders/sdf.rectmask0.frag.spv differ diff --git a/src/figdraw/vulkan/shaders/sdf.rectmask0.vert.spv b/src/figdraw/vulkan/shaders/sdf.rectmask0.vert.spv new file mode 100644 index 0000000..3daa9ab Binary files /dev/null and b/src/figdraw/vulkan/shaders/sdf.rectmask0.vert.spv differ diff --git a/src/figdraw/vulkan/shaders/sdf.rectmask1.frag.spv b/src/figdraw/vulkan/shaders/sdf.rectmask1.frag.spv new file mode 100644 index 0000000..c6599d9 Binary files /dev/null and b/src/figdraw/vulkan/shaders/sdf.rectmask1.frag.spv differ diff --git a/src/figdraw/vulkan/shaders/sdf.rectmask1.vert.spv b/src/figdraw/vulkan/shaders/sdf.rectmask1.vert.spv new file mode 100644 index 0000000..3daa9ab Binary files /dev/null and b/src/figdraw/vulkan/shaders/sdf.rectmask1.vert.spv differ diff --git a/src/figdraw/vulkan/shaders/sdf.vert.glsl b/src/figdraw/vulkan/shaders/sdf.vert.glsl index b9f6af3..4b9075a 100644 --- a/src/figdraw/vulkan/shaders/sdf.vert.glsl +++ b/src/figdraw/vulkan/shaders/sdf.vert.glsl @@ -17,6 +17,12 @@ layout(location = 9) in vec4 vertexRectMaskParams; layout(location = 10) in vec4 vertexRectMaskRadii; layout(location = 11) in vec4 vertexRectMaskMatX; layout(location = 12) in vec4 vertexRectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +layout(location = 13) in vec4 vertexRectMaskParams2; +layout(location = 14) in vec4 vertexRectMaskRadii2; +layout(location = 15) in vec4 vertexRectMaskMatX2; +layout(location = 16) in vec4 vertexRectMaskMatY2; +#endif layout(location = 0) out vec2 vPos; layout(location = 1) out vec2 vUv; @@ -31,6 +37,12 @@ layout(location = 9) out vec4 vRectMaskParams; layout(location = 10) out vec4 vRectMaskRadii; layout(location = 11) out vec4 vRectMaskMatX; layout(location = 12) out vec4 vRectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 +layout(location = 13) out vec4 vRectMaskParams2; +layout(location = 14) out vec4 vRectMaskRadii2; +layout(location = 15) out vec4 vRectMaskMatX2; +layout(location = 16) out vec4 vRectMaskMatY2; +#endif void main() { vPos = vertexPos; @@ -46,6 +58,12 @@ void main() { vRectMaskRadii = vertexRectMaskRadii; vRectMaskMatX = vertexRectMaskMatX; vRectMaskMatY = vertexRectMaskMatY; +#if FIGDRAW_FAST_RECT_MASK_LIMIT >= 2 + vRectMaskParams2 = vertexRectMaskParams2; + vRectMaskRadii2 = vertexRectMaskRadii2; + vRectMaskMatX2 = vertexRectMaskMatX2; + vRectMaskMatY2 = vertexRectMaskMatY2; +#endif vec4 p = uVS.proj * vec4(vertexPos.xy, 0.0, 1.0); p.y = -p.y; diff --git a/src/figdraw/vulkan/shaders/sdf.vert.spv b/src/figdraw/vulkan/shaders/sdf.vert.spv index 1e4dc5e..0127216 100644 Binary files a/src/figdraw/vulkan/shaders/sdf.vert.spv and b/src/figdraw/vulkan/shaders/sdf.vert.spv differ diff --git a/src/figdraw/vulkan/vulkan_context.nim b/src/figdraw/vulkan/vulkan_context.nim index 03667b6..5217273 100644 --- a/src/figdraw/vulkan/vulkan_context.nim +++ b/src/figdraw/vulkan/vulkan_context.nim @@ -22,11 +22,23 @@ logScope: const quadLimit = 10_921 - sdfVertSpv = staticRead("shaders/sdf.vert.spv") - sdfFragSpv = staticRead("shaders/sdf.frag.spv") + fastRectMaskLimit = figbackend.FigDrawFastRectMaskLimit blurVertSpv = staticRead("shaders/blur.vert.spv") blurFragSpv = staticRead("shaders/blur.frag.spv") +when fastRectMaskLimit == 0: + const + sdfVertSpv = staticRead("shaders/sdf.rectmask0.vert.spv") + sdfFragSpv = staticRead("shaders/sdf.rectmask0.frag.spv") +elif fastRectMaskLimit == 1: + const + sdfVertSpv = staticRead("shaders/sdf.rectmask1.vert.spv") + sdfFragSpv = staticRead("shaders/sdf.rectmask1.frag.spv") +else: + const + sdfVertSpv = staticRead("shaders/sdf.vert.spv") + sdfFragSpv = staticRead("shaders/sdf.frag.spv") + when defined(emscripten): type SdfModeData = float32 else: @@ -86,6 +98,11 @@ type rectMaskRadii: array[4, float32] rectMaskMatX: array[4, float32] rectMaskMatY: array[4, float32] + when fastRectMaskLimit >= 2: + rectMaskParams2: array[4, float32] + rectMaskRadii2: array[4, float32] + rectMaskMatX2: array[4, float32] + rectMaskMatY2: array[4, float32] VulkanContext* = ref object of figbackend.BackendContext atlasSize: int @@ -125,6 +142,11 @@ type rectMaskRadii: seq[float32] rectMaskMatX: seq[float32] rectMaskMatY: seq[float32] + when fastRectMaskLimit >= 2: + rectMaskParams2: seq[float32] + rectMaskRadii2: seq[float32] + rectMaskMatX2: seq[float32] + rectMaskMatY2: seq[float32] rectMaskStack: seq[RectMask] batchHasRectMask: bool indices: seq[uint16] @@ -768,86 +790,120 @@ proc createPipeline(ctx: VulkanContext) = binding: 0, stride: uint32(sizeof(Vertex)), inputRate: VK_VERTEX_INPUT_RATE_VERTEX ) - let attrDescs = [ - VkVertexInputAttributeDescription( - location: 0, - binding: 0, - format: VK_FORMAT_R32G32_SFLOAT, - offset: uint32(offsetOf(Vertex, pos)), - ), - VkVertexInputAttributeDescription( - location: 1, - binding: 0, - format: VK_FORMAT_R32G32_SFLOAT, - offset: uint32(offsetOf(Vertex, uv)), - ), - VkVertexInputAttributeDescription( - location: 2, - binding: 0, - format: VK_FORMAT_R8G8B8A8_UNORM, - offset: uint32(offsetOf(Vertex, color)), - ), - VkVertexInputAttributeDescription( - location: 3, - binding: 0, - format: VK_FORMAT_R8G8B8A8_UNORM, - offset: uint32(offsetOf(Vertex, fillMidColor)), - ), - VkVertexInputAttributeDescription( - location: 4, - binding: 0, - format: VK_FORMAT_R8G8B8A8_UNORM, - offset: uint32(offsetOf(Vertex, fillStopColor)), - ), - VkVertexInputAttributeDescription( - location: 5, - binding: 0, - format: VK_FORMAT_R32G32B32A32_SFLOAT, - offset: uint32(offsetOf(Vertex, sdfParams)), - ), - VkVertexInputAttributeDescription( - location: 6, - binding: 0, - format: VK_FORMAT_R32G32B32A32_SFLOAT, - offset: uint32(offsetOf(Vertex, sdfRadii)), - ), - VkVertexInputAttributeDescription( - location: 7, - binding: 0, - format: VK_FORMAT_R16_UINT, - offset: uint32(offsetOf(Vertex, sdfMode)), - ), - VkVertexInputAttributeDescription( - location: 8, - binding: 0, - format: VK_FORMAT_R32G32_SFLOAT, - offset: uint32(offsetOf(Vertex, sdfFactors)), - ), - VkVertexInputAttributeDescription( - location: 9, - binding: 0, - format: VK_FORMAT_R32G32B32A32_SFLOAT, - offset: uint32(offsetOf(Vertex, rectMaskParams)), - ), - VkVertexInputAttributeDescription( - location: 10, - binding: 0, - format: VK_FORMAT_R32G32B32A32_SFLOAT, - offset: uint32(offsetOf(Vertex, rectMaskRadii)), - ), - VkVertexInputAttributeDescription( - location: 11, - binding: 0, - format: VK_FORMAT_R32G32B32A32_SFLOAT, - offset: uint32(offsetOf(Vertex, rectMaskMatX)), - ), - VkVertexInputAttributeDescription( - location: 12, - binding: 0, - format: VK_FORMAT_R32G32B32A32_SFLOAT, - offset: uint32(offsetOf(Vertex, rectMaskMatY)), - ), - ] + var attrDescs = + @[ + VkVertexInputAttributeDescription( + location: 0, + binding: 0, + format: VK_FORMAT_R32G32_SFLOAT, + offset: uint32(offsetOf(Vertex, pos)), + ), + VkVertexInputAttributeDescription( + location: 1, + binding: 0, + format: VK_FORMAT_R32G32_SFLOAT, + offset: uint32(offsetOf(Vertex, uv)), + ), + VkVertexInputAttributeDescription( + location: 2, + binding: 0, + format: VK_FORMAT_R8G8B8A8_UNORM, + offset: uint32(offsetOf(Vertex, color)), + ), + VkVertexInputAttributeDescription( + location: 3, + binding: 0, + format: VK_FORMAT_R8G8B8A8_UNORM, + offset: uint32(offsetOf(Vertex, fillMidColor)), + ), + VkVertexInputAttributeDescription( + location: 4, + binding: 0, + format: VK_FORMAT_R8G8B8A8_UNORM, + offset: uint32(offsetOf(Vertex, fillStopColor)), + ), + VkVertexInputAttributeDescription( + location: 5, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, sdfParams)), + ), + VkVertexInputAttributeDescription( + location: 6, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, sdfRadii)), + ), + VkVertexInputAttributeDescription( + location: 7, + binding: 0, + format: VK_FORMAT_R16_UINT, + offset: uint32(offsetOf(Vertex, sdfMode)), + ), + VkVertexInputAttributeDescription( + location: 8, + binding: 0, + format: VK_FORMAT_R32G32_SFLOAT, + offset: uint32(offsetOf(Vertex, sdfFactors)), + ), + VkVertexInputAttributeDescription( + location: 9, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, rectMaskParams)), + ), + VkVertexInputAttributeDescription( + location: 10, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, rectMaskRadii)), + ), + VkVertexInputAttributeDescription( + location: 11, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, rectMaskMatX)), + ), + VkVertexInputAttributeDescription( + location: 12, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, rectMaskMatY)), + ), + ] + when fastRectMaskLimit >= 2: + attrDescs.add( + VkVertexInputAttributeDescription( + location: 13, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, rectMaskParams2)), + ) + ) + attrDescs.add( + VkVertexInputAttributeDescription( + location: 14, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, rectMaskRadii2)), + ) + ) + attrDescs.add( + VkVertexInputAttributeDescription( + location: 15, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, rectMaskMatX2)), + ) + ) + attrDescs.add( + VkVertexInputAttributeDescription( + location: 16, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: uint32(offsetOf(Vertex, rectMaskMatY2)), + ) + ) let vertexInputInfo = newVkPipelineVertexInputStateCreateInfo( vertexBindingDescriptions = [bindingDesc], vertexAttributeDescriptions = attrDescs @@ -2001,6 +2057,23 @@ proc flush(ctx: VulkanContext) = v.rectMaskMatY[1] = ctx.rectMaskMatY[i * 4 + 1] v.rectMaskMatY[2] = ctx.rectMaskMatY[i * 4 + 2] v.rectMaskMatY[3] = ctx.rectMaskMatY[i * 4 + 3] + when fastRectMaskLimit >= 2: + v.rectMaskParams2[0] = ctx.rectMaskParams2[i * 4 + 0] + v.rectMaskParams2[1] = ctx.rectMaskParams2[i * 4 + 1] + v.rectMaskParams2[2] = ctx.rectMaskParams2[i * 4 + 2] + v.rectMaskParams2[3] = ctx.rectMaskParams2[i * 4 + 3] + v.rectMaskRadii2[0] = ctx.rectMaskRadii2[i * 4 + 0] + v.rectMaskRadii2[1] = ctx.rectMaskRadii2[i * 4 + 1] + v.rectMaskRadii2[2] = ctx.rectMaskRadii2[i * 4 + 2] + v.rectMaskRadii2[3] = ctx.rectMaskRadii2[i * 4 + 3] + v.rectMaskMatX2[0] = ctx.rectMaskMatX2[i * 4 + 0] + v.rectMaskMatX2[1] = ctx.rectMaskMatX2[i * 4 + 1] + v.rectMaskMatX2[2] = ctx.rectMaskMatX2[i * 4 + 2] + v.rectMaskMatX2[3] = ctx.rectMaskMatX2[i * 4 + 3] + v.rectMaskMatY2[0] = ctx.rectMaskMatY2[i * 4 + 0] + v.rectMaskMatY2[1] = ctx.rectMaskMatY2[i * 4 + 1] + v.rectMaskMatY2[2] = ctx.rectMaskMatY2[i * 4 + 2] + v.rectMaskMatY2[3] = ctx.rectMaskMatY2[i * 4 + 3] let uploadBytes = VkDeviceSize(vertexCount * sizeof(Vertex)) let vertexAlloc = ctx.createBuffer( @@ -2175,6 +2248,16 @@ proc setRectMaskVert4( ctx.rectMaskMatX.setVert4(offset + i, matX) ctx.rectMaskMatY.setVert4(offset + i, matY) +when fastRectMaskLimit >= 2: + proc setRectMask2Vert4( + ctx: VulkanContext, offset: int, params, radii, matX, matY: Vec4 + ) = + for i in 0 ..< 4: + ctx.rectMaskParams2.setVert4(offset + i, params) + ctx.rectMaskRadii2.setVert4(offset + i, radii) + ctx.rectMaskMatX2.setVert4(offset + i, matX) + ctx.rectMaskMatY2.setVert4(offset + i, matY) + proc setDisabledRectMaskVerts(ctx: VulkanContext, firstVertex, vertexCount: int) = let params = vec4(0.0'f32, 0.0'f32, -1.0'f32, -1.0'f32) @@ -2184,40 +2267,77 @@ proc setDisabledRectMaskVerts(ctx: VulkanContext, firstVertex, vertexCount: int) ctx.rectMaskRadii.setVert4(i, zero4) ctx.rectMaskMatX.setVert4(i, zero4) ctx.rectMaskMatY.setVert4(i, zero4) + when fastRectMaskLimit >= 2: + ctx.rectMaskParams2.setVert4(i, params) + ctx.rectMaskRadii2.setVert4(i, zero4) + ctx.rectMaskMatX2.setVert4(i, zero4) + ctx.rectMaskMatY2.setVert4(i, zero4) + +proc fastRectMaskCount(ctx: VulkanContext): int = + for rectMask in ctx.rectMaskStack: + if rectMask.kind == rmkFast: + inc result proc setRectMaskVert4(ctx: VulkanContext, offset: int) = - if ctx.maskBegun: + when fastRectMaskLimit == 0: return + else: + if ctx.maskBegun: + return - var - hasRectMask = false - params = vec4(0.0'f32) - radii = vec4(0.0'f32) - matX = vec4(0.0'f32) - matY = vec4(0.0'f32) - - if ctx.rectMaskStack.len > 0: - for i in countdown(ctx.rectMaskStack.len - 1, 0): - let rectMask = ctx.rectMaskStack[i] - if rectMask.kind == rmkFast: - hasRectMask = true - params = rectMask.params - radii = rectMask.radii - matX = rectMask.matX - matY = rectMask.matY - break - - if hasRectMask: - if not ctx.batchHasRectMask: - ctx.setDisabledRectMaskVerts(0, offset) - ctx.batchHasRectMask = true - ctx.setRectMaskVert4(offset, params, radii, matX, matY) - elif ctx.batchHasRectMask: - ctx.setDisabledRectMaskVerts(offset, 4) + var + maskCount = 0 + params = vec4(0.0'f32) + radii = vec4(0.0'f32) + matX = vec4(0.0'f32) + matY = vec4(0.0'f32) + when fastRectMaskLimit >= 2: + var + params2 = vec4(0.0'f32) + radii2 = vec4(0.0'f32) + matX2 = vec4(0.0'f32) + matY2 = vec4(0.0'f32) + + if ctx.rectMaskStack.len > 0: + for i in countdown(ctx.rectMaskStack.len - 1, 0): + let rectMask = ctx.rectMaskStack[i] + if rectMask.kind == rmkFast: + if maskCount == 0: + params = rectMask.params + radii = rectMask.radii + matX = rectMask.matX + matY = rectMask.matY + inc maskCount + elif fastRectMaskLimit >= 2 and maskCount == 1: + when fastRectMaskLimit >= 2: + params2 = rectMask.params + radii2 = rectMask.radii + matX2 = rectMask.matX + matY2 = rectMask.matY + inc maskCount + if maskCount >= fastRectMaskLimit: + break + + if maskCount > 0: + if not ctx.batchHasRectMask: + ctx.setDisabledRectMaskVerts(0, offset) + ctx.batchHasRectMask = true + ctx.setRectMaskVert4(offset, params, radii, matX, matY) + when fastRectMaskLimit >= 2: + if maskCount >= 2: + ctx.setRectMask2Vert4(offset, params2, radii2, matX2, matY2) + else: + let + disabledParams = vec4(0.0'f32, 0.0'f32, -1.0'f32, -1.0'f32) + zero4 = vec4(0.0'f32) + ctx.setRectMask2Vert4(offset, disabledParams, zero4, zero4, zero4) + elif ctx.batchHasRectMask: + ctx.setDisabledRectMaskVerts(offset, 4) template setRectMaskVert4IfNeeded(ctx: VulkanContext, offset: int) = - if not ctx.maskBegun and (ctx.batchHasRectMask or ctx.rectMaskStack.len > 0): - ctx.setRectMaskVert4(offset) + when fastRectMaskLimit > 0: + if not ctx.maskBegun and (ctx.batchHasRectMask or ctx.rectMaskStack.len > 0): + ctx.setRectMaskVert4(offset) proc copyIntoAtlas(atlas: Image, atX, atY: int, image: Image) = for y in 0 ..< image.height: @@ -3282,7 +3402,8 @@ method beginRectMask*( assert ctx.frameBegun == true, "ctx.beginFrame has not been called." assert ctx.maskBegun == false, "ctx.beginRectMask cannot start inside a mask." - if ctx.rectMaskStack.len == 0 and maskRect.w > 0.0'f32 and maskRect.h > 0.0'f32: + if fastRectMaskLimit > 0 and ctx.fastRectMaskCount() < fastRectMaskLimit and + maskRect.w > 0.0'f32 and maskRect.h > 0.0'f32: ctx.rectMaskStack.add(ctx.makeRectMask(maskRect, radii)) else: ctx.beginMask(maskRect, radii) @@ -3637,6 +3758,11 @@ proc newContext*( result.rectMaskRadii = newSeq[float32](4 * maxQuads * 4) result.rectMaskMatX = newSeq[float32](4 * maxQuads * 4) result.rectMaskMatY = newSeq[float32](4 * maxQuads * 4) + when fastRectMaskLimit >= 2: + result.rectMaskParams2 = newSeq[float32](4 * maxQuads * 4) + result.rectMaskRadii2 = newSeq[float32](4 * maxQuads * 4) + result.rectMaskMatX2 = newSeq[float32](4 * maxQuads * 4) + result.rectMaskMatY2 = newSeq[float32](4 * maxQuads * 4) result.vertexScratch = newSeq[Vertex](maxQuads * 4) result.indices = newSeq[uint16](maxQuads * 6)