diff --git a/nimby.lock b/nimby.lock index a1511d2..f4c3fb7 100644 --- a/nimby.lock +++ b/nimby.lock @@ -1,4 +1,4 @@ -shady 0.1.4 https://github.com/treeform/shady 4638b43517ac3afa474514746f2640551ad0694c +shady 0.1.5 https://github.com/treeform/shady f9ab26d946c32cd9da7c04394a1353a375a9dba5 vmath 2.0.1 https://github.com/treeform/vmath ddfcd738578c47ed3330b917f5efecf5a15128b2 pixie 5.1.0 https://github.com/treeform/pixie 5eda4949a3c8bea318cfac8e42060a8f90f3f35d chroma 1.0.0 https://github.com/treeform/chroma 2381748f92e5ea16cb2403ff7e20c6dd5443a59d diff --git a/silky.nimble b/silky.nimble index ec3f93c..dfb1fb0 100644 --- a/silky.nimble +++ b/silky.nimble @@ -7,7 +7,7 @@ srcDir = "src" requires "nim >= 2.2.4" requires "pixie" -requires "shady >= 0.1.5" +requires "shady" requires "vmath" requires "windy" requires "dx12" diff --git a/src/silky/drawers/dx12.nim b/src/silky/drawers/dx12.nim index 6e1225a..763ca8b 100644 --- a/src/silky/drawers/dx12.nim +++ b/src/silky/drawers/dx12.nim @@ -1,12 +1,24 @@ when not defined(windows): {.error: "The Silky DirectX 12 backend requires Windows.".} -import - pixie, vmath, windy, - pkg/dx12, pkg/dx12/context - -const - InitialVertexCapacity = 4096 +import + pixie, vmath, windy, + pkg/dx12, pkg/dx12/context + +when not defined(shadyBinaryShaders): + import std/os, silky/drawers/shader + from shady import compileHlslShader, toHLSL, shaderVertex, shaderFragment + +const + InitialVertexCapacity = 4096 + +when not defined(shadyBinaryShaders): + const + ShaderDir = currentSourcePath().parentDir / "shaders" + VertexHlslPath = ShaderDir / "silky.vert.hlsl" + PixelHlslPath = ShaderDir / "silky.frag.hlsl" + VertexCsoPath = ShaderDir / "silky.vs.cso" + PixelCsoPath = ShaderDir / "silky.ps.cso" type DrawerVertex* {.packed.} = object @@ -32,34 +44,22 @@ type vertexBufferPtr: pointer maxVertexCount: int viewportSize: IVec2 - clearColor: array[4, FLOAT] + clearColor: array[4, float32] layers*: array[2, seq[DrawerVertex]] currentLayer*: int layerStack*: seq[int] -proc clampViewport(size: IVec2): IVec2 = - ## Clamps the viewport to valid swap-chain dimensions. - ivec2(max(1'i32, size.x), max(1'i32, size.y)) - -proc normalizeVertices( - vertices: var seq[DrawerVertex], - viewportSize: IVec2, - atlasSize: Vec2 -) = - ## Converts queued pixel-space vertices to clip-space and normalized UVs. - let - width = max(1.0'f, viewportSize.x.float32) - height = max(1.0'f, viewportSize.y.float32) - for i in 0 ..< vertices.len: - let p = vertices[i].pos - vertices[i].pos = vec2( - (p.x / width) * 2.0'f - 1.0'f, - 1.0'f - (p.y / height) * 2.0'f - ) - vertices[i].uv = vertices[i].uv / atlasSize - vertices[i].maskUv = vertices[i].maskUv / atlasSize - -proc createVertexBuffer(state: Drawer, maxVertexCount: int) = +proc clampViewport(size: IVec2): IVec2 = + ## Clamps the viewport to valid swap-chain dimensions. + ivec2(max(1'i32, size.x), max(1'i32, size.y)) + +proc shaderBytecode(code: string): D3D12_SHADER_BYTECODE = + D3D12_SHADER_BYTECODE( + pShaderBytecode: unsafeAddr code[0], + BytecodeLength: csize_t(code.len) + ) + +proc createVertexBuffer(state: Drawer, maxVertexCount: int) = ## Creates or replaces the persistently mapped upload vertex buffer. if state.vertexBuffer != nil: state.vertexBuffer.unmap(0, nil) @@ -67,7 +67,7 @@ proc createVertexBuffer(state: Drawer, maxVertexCount: int) = state.vertexBuffer = nil state.vertexBufferPtr = nil - let vertexBufferSize = UINT64(maxVertexCount * sizeof(DrawerVertex)) + let vertexBufferSize = uint64(maxVertexCount * sizeof(DrawerVertex)) var bufferDesc: D3D12_RESOURCE_DESC zeroMem(addr bufferDesc, sizeof(bufferDesc)) @@ -139,9 +139,9 @@ proc uploadTexture(state: Drawer, image: Image) = ) var footprint = D3D12_PLACED_SUBRESOURCE_FOOTPRINT() - var numRows: uint32 - var rowSize: UINT64 - var totalBytes: UINT64 + var numRows: uint32 + var rowSize: uint64 + var totalBytes: uint64 state.ctx.device.getCopyableFootprints( addr texDesc, 0, @@ -271,72 +271,43 @@ proc uploadTexture(state: Drawer, image: Image) = srvCpuHandle ) -proc initRenderer(state: Drawer, image: Image, size: IVec2) = - ## Creates the DX12 pipeline, upload buffers, and atlas texture. - const vertexShaderSrc = """ -struct VSInput { - float2 pos : POSITION; - float2 uv : TEXCOORD0; - float4 color : COLOR0; - float2 clipPos : TEXCOORD1; - float2 clipSize : TEXCOORD2; - float2 maskUv : TEXCOORD3; -}; - -struct PSInput { - float4 pos : SV_POSITION; - float2 uv : TEXCOORD0; - float4 color : COLOR0; - float2 clipPos : TEXCOORD1; - float2 clipSize : TEXCOORD2; - float2 maskUv : TEXCOORD3; -}; - -PSInput VSMain(VSInput input) { - PSInput output; - output.pos = float4(input.pos, 0.0f, 1.0f); - output.uv = input.uv; - output.color = input.color; - output.clipPos = input.clipPos; - output.clipSize = input.clipSize; - output.maskUv = input.maskUv; - return output; -} -""" - - const pixelShaderSrc = """ -Texture2D tex0 : register(t0); -SamplerState samp0 : register(s0); - -struct PSInput { - float4 pos : SV_POSITION; - float2 uv : TEXCOORD0; - float4 color : COLOR0; - float2 clipPos : TEXCOORD1; - float2 clipSize : TEXCOORD2; - float2 maskUv : TEXCOORD3; -}; - -float4 PSMain(PSInput input) : SV_TARGET { - if (input.pos.x < input.clipPos.x || - input.pos.y < input.clipPos.y || - input.pos.x > input.clipPos.x + input.clipSize.x || - input.pos.y > input.clipPos.y + input.clipSize.y) { - discard; - } - float4 base = tex0.Sample(samp0, input.uv); - if (input.maskUv.x >= 0.0) { - float maskR = tex0.Sample(samp0, input.maskUv).r; - return float4(base.rgb * lerp(float3(1,1,1), input.color.rgb, maskR), base.a * input.color.a); - } - return base * input.color; -} -""" - - let - safeSize = clampViewport(size) - vsBlob = compileShader(vertexShaderSrc, "VSMain", "vs_5_0") - psBlob = compileShader(pixelShaderSrc, "PSMain", "ps_5_0") +proc initRenderer(state: Drawer, image: Image, size: IVec2) = + ## Creates the DX12 pipeline, upload buffers, and atlas texture. + when defined(shadyBinaryShaders): + const + vertexShaderCode = staticRead("shaders/silky.vs.cso") + pixelShaderCode = staticRead("shaders/silky.ps.cso") + else: + const + vertexShaderSrc = toHLSL(silkyVert, shaderVertex) + pixelShaderSrc = toHLSL(silkyFrag, shaderFragment) + vertexShaderCode = compileHlslShader( + vertexShaderSrc, + VertexHlslPath, + VertexCsoPath, + "VSMain", + "vs_6_0" + ) + pixelShaderCode = compileHlslShader( + pixelShaderSrc, + PixelHlslPath, + PixelCsoPath, + "PSMain", + "ps_6_0" + ) + let + safeSize = clampViewport(size) + + when defined(shadyBinaryShaders): + let + vsBytecode = shaderBytecode(vertexShaderCode) + psBytecode = shaderBytecode(pixelShaderCode) + else: + let + vsBlob = compileShader(vertexShaderSrc, "VSMain", "vs_5_0") + psBlob = compileShader(pixelShaderSrc, "PSMain", "ps_5_0") + vsBytecode = shaderBytecode(vsBlob) + psBytecode = shaderBytecode(psBlob) var range = D3D12_DESCRIPTOR_RANGE( RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV, @@ -347,10 +318,21 @@ float4 PSMain(PSInput input) : SV_TARGET { D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND ) - var rootParams = [ - D3D12_ROOT_PARAMETER( - ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE, - data: D3D12_ROOT_PARAMETER_UNION( + var rootParams = [ + D3D12_ROOT_PARAMETER( + ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, + data: D3D12_ROOT_PARAMETER_UNION( + Constants: D3D12_ROOT_CONSTANTS( + ShaderRegister: 0, + RegisterSpace: 0, + Num32BitValues: 4 + ) + ), + ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX + ), + D3D12_ROOT_PARAMETER( + ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE, + data: D3D12_ROOT_PARAMETER_UNION( DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE( NumDescriptorRanges: 1, pDescriptorRanges: addr range @@ -472,9 +454,9 @@ float4 PSMain(PSInput input) : SV_TARGET { ) var psoDesc = D3D12_GRAPHICS_PIPELINE_STATE_DESC( - pRootSignature: state.rootSignature, - VS: shaderBytecode(vsBlob), - PS: shaderBytecode(psBlob), + pRootSignature: state.rootSignature, + VS: vsBytecode, + PS: psBytecode, StreamOutput: D3D12_STREAM_OUTPUT_DESC(), BlendState: blendDesc, SampleMask: D3D12_DEFAULT_SAMPLE_MASK, @@ -519,8 +501,9 @@ float4 PSMain(PSInput input) : SV_TARGET { addr psoDesc ) - release(vsBlob) - release(psBlob) + when not defined(shadyBinaryShaders): + release(vsBlob) + release(psBlob) state.viewportSize = safeSize state.createVertexBuffer(InitialVertexCapacity) @@ -565,7 +548,7 @@ proc ensureVertexCapacity(state: Drawer, vertexCount: int) = newCapacity *= 2 state.createVertexBuffer(newCapacity) -proc recordDraw(state: Drawer, vertexCount: int) = +proc recordDraw(state: Drawer, vertexCount: int, atlasSize: Vec2) = ## Records the DX12 draw pass for the current frame. state.ctx.commandAllocator.reset() state.ctx.commandList.reset(state.ctx.commandAllocator, state.pipelineState) @@ -598,12 +581,24 @@ proc recordDraw(state: Drawer, vertexCount: int) = nil ) - var heaps = [state.srvHeap] - state.ctx.commandList.setDescriptorHeaps(1, addr heaps[0]) - state.ctx.commandList.setGraphicsRootDescriptorTable( - 0, - state.srvHandleGpu - ) + var heaps = [state.srvHeap] + state.ctx.commandList.setDescriptorHeaps(1, addr heaps[0]) + var shaderUniforms = [ + state.viewportSize.x.float32, + state.viewportSize.y.float32, + atlasSize.x, + atlasSize.y + ] + state.ctx.commandList.setGraphicsRoot32BitConstants( + 0, + uint32(shaderUniforms.len), + unsafeAddr shaderUniforms[0], + 0 + ) + state.ctx.commandList.setGraphicsRootDescriptorTable( + 1, + state.srvHandleGpu + ) state.ctx.commandList.iaSetPrimitiveTopology( D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST ) @@ -638,16 +633,15 @@ proc endFrame*( let quadsArr = cast[ptr UncheckedArray[DrawerVertex]](quads) for i in 0 ..< quadCount: vertices.add(quadsArr[i]) - vertices.normalizeVertices(drawer.viewportSize, atlasSize) - - if vertexCount > 0: + + if vertexCount > 0: copyMem( drawer.vertexBufferPtr, unsafeAddr vertices[0], vertexCount * sizeof(DrawerVertex) ) - drawer.recordDraw(vertexCount) + drawer.recordDraw(vertexCount, atlasSize) drawer.ctx.executeFrame( if drawer.window != nil: drawer.window.vsync else: true ) diff --git a/src/silky/drawers/ogl.nim b/src/silky/drawers/ogl.nim index 25b40b3..8b652c2 100644 --- a/src/silky/drawers/ogl.nim +++ b/src/silky/drawers/ogl.nim @@ -1,6 +1,7 @@ -import - std/[strformat, strutils], - pixie, opengl, shady, vmath, windy +import + std/[strformat, strutils], + pixie, opengl, shady, vmath, windy, + silky/drawers/shader type BufferKind* = enum @@ -50,12 +51,7 @@ type currentLayer*: int layerStack*: seq[int] -var - mvp: Uniform[Mat4] - atlasSize: Uniform[Vec2] - atlasSampler: Uniform[Sampler2D] - -func size(componentType: GLenum): Positive = +func size(componentType: GLenum): Positive = ## Returns the byte size of a GL component type. case componentType: of cGL_BYTE, cGL_UNSIGNED_BYTE: @@ -467,55 +463,7 @@ proc bindUniforms*(shader: Shader) = uniform.changed = false -proc silkyVert( - pos: Vec2, - uv: Vec2, - color: ColorRGBX, - clipPos: Vec2, - clipSize: Vec2, - maskUv: Vec2, - fragmentUv: var Vec2, - fragmentColor: var Vec4, - fragmentClipPos: var Vec2, - fragmentClipSize: var Vec2, - fragmentPos: var Vec2, - fragmentMaskUv: var Vec2 -) = - ## Vertex shader for Silky's OpenGL drawer. - gl_Position = mvp * vec4(pos.x, pos.y, 0.0, 1.0) - fragmentUv = uv / atlasSize - fragmentColor = color.vec4 - fragmentClipPos = clipPos - fragmentClipSize = clipSize - fragmentPos = pos - fragmentMaskUv = maskUv / atlasSize - -proc silkyFrag( - fragmentUv: Vec2, - fragmentColor: Vec4, - fragmentClipPos: Vec2, - fragmentClipSize: Vec2, - fragmentPos: Vec2, - fragmentMaskUv: Vec2, - fragColor: var Vec4 -) = - ## Fragment shader for Silky's OpenGL drawer. - if fragmentPos.x < fragmentClipPos.x or - fragmentPos.y < fragmentClipPos.y or - fragmentPos.x > fragmentClipPos.x + fragmentClipSize.x or - fragmentPos.y > fragmentClipPos.y + fragmentClipSize.y: - discardFragment() - elif fragmentMaskUv.x >= 0.0: - let base = texture(atlasSampler, fragmentUv) - let maskR = texture(atlasSampler, fragmentMaskUv).r - fragColor = vec4( - base.rgb * mix(vec3(1.0), fragmentColor.rgb, maskR), - base.a * fragmentColor.a - ) - else: - fragColor = texture(atlasSampler, fragmentUv) * fragmentColor - -proc newDrawer*(window: Window, image: Image): Drawer = +proc newDrawer*(window: Window, image: Image): Drawer = ## Creates a new OpenGL drawer and eagerly uploads its resources. discard window result = Drawer() @@ -670,12 +618,11 @@ proc endFrame*( GL_STREAM_DRAW ) - glUseProgram(drawer.shader.programId) - mvp = ortho(0.0'f, size.x, size.y, 0.0'f, -1000.0, 1000.0) - drawer.shader.setUniform("mvp", mvp) - drawer.shader.setUniform( - "atlasSize", - vec2(image.width.float32, image.height.float32) + glUseProgram(drawer.shader.programId) + drawer.shader.setUniform("viewportSize", size) + drawer.shader.setUniform( + "atlasSize", + vec2(image.width.float32, image.height.float32) ) glActiveTexture(GL_TEXTURE0) glBindTexture(GL_TEXTURE_2D, drawer.atlasTexture) diff --git a/src/silky/drawers/shader.nim b/src/silky/drawers/shader.nim new file mode 100644 index 0000000..4dd4a58 --- /dev/null +++ b/src/silky/drawers/shader.nim @@ -0,0 +1,57 @@ +import pixie, shady, vmath + +var + viewportSize*: Uniform[Vec2] + atlasSize*: Uniform[Vec2] + atlasSampler*: Uniform[Sampler2D] + +proc silkyVert*( + pos: Vec2, + uv: Vec2, + color: ColorRGBX, + clipPos: Vec2, + clipSize: Vec2, + maskUv: Vec2, + gl_Position: var Vec4, + fragmentUv: var Vec2, + fragmentColor: var Vec4, + fragmentClipPos: var Vec2, + fragmentClipSize: var Vec2, + fragmentPos: var Vec2, + fragmentMaskUv: var Vec2 +) = + let ndc = vec2( + pos.x / viewportSize.x * 2.0'f32 - 1.0'f32, + 1.0'f32 - pos.y / viewportSize.y * 2.0'f32 + ) + gl_Position = vec4(ndc.x, ndc.y, 0.0, 1.0) + fragmentUv = uv / atlasSize + fragmentColor = vec4(color) + fragmentClipPos = clipPos + fragmentClipSize = clipSize + fragmentPos = pos + fragmentMaskUv = maskUv / atlasSize + +proc silkyFrag*( + fragmentUv: Vec2, + fragmentColor: Vec4, + fragmentClipPos: Vec2, + fragmentClipSize: Vec2, + fragmentPos: Vec2, + fragmentMaskUv: Vec2, + fragColor: var Vec4 +) = + if fragmentPos.x < fragmentClipPos.x or + fragmentPos.y < fragmentClipPos.y or + fragmentPos.x > fragmentClipPos.x + fragmentClipSize.x or + fragmentPos.y > fragmentClipPos.y + fragmentClipSize.y: + discardFragment() + elif fragmentMaskUv.x >= 0.0: + let base = texture(atlasSampler, fragmentUv) + let maskR = texture(atlasSampler, fragmentMaskUv).r + fragColor = vec4( + base.rgb * mix(vec3(1.0, 1.0, 1.0), fragmentColor.rgb, maskR), + base.a * fragmentColor.a + ) + else: + fragColor = texture(atlasSampler, fragmentUv) * fragmentColor diff --git a/src/silky/drawers/shaders/silky.frag b/src/silky/drawers/shaders/silky.frag index 46ee489..560ef28 100644 --- a/src/silky/drawers/shaders/silky.frag +++ b/src/silky/drawers/shaders/silky.frag @@ -1,28 +1,24 @@ -#version 450 - -layout(set = 0, binding = 0) uniform sampler2D tex0; - -layout(location = 0) in vec2 fragUv; -layout(location = 1) in vec4 fragColor; -layout(location = 2) in vec2 fragClipPos; -layout(location = 3) in vec2 fragClipSize; -layout(location = 4) in vec2 fragPos; -layout(location = 5) in vec2 fragMaskUv; - -layout(location = 0) out vec4 outColor; - -void main() { - if (fragPos.x < fragClipPos.x || - fragPos.y < fragClipPos.y || - fragPos.x > fragClipPos.x + fragClipSize.x || - fragPos.y > fragClipPos.y + fragClipSize.y) { - discard; - } - vec4 base = texture(tex0, fragUv); - if (fragMaskUv.x >= 0.0) { - float maskR = texture(tex0, fragMaskUv).r; - outColor = vec4(base.rgb * mix(vec3(1.0), fragColor.rgb, maskR), base.a * fragColor.a); - } else { - outColor = base * fragColor; - } -} +#version 450 +// from silkyFrag + +layout(set = 0, binding = 0) uniform sampler2D atlasSampler; + +layout(location = 0) in vec2 fragmentUv; +layout(location = 1) in vec4 fragmentColor; +layout(location = 2) in vec2 fragmentClipPos; +layout(location = 3) in vec2 fragmentClipSize; +layout(location = 4) in vec2 fragmentPos; +layout(location = 5) in vec2 fragmentMaskUv; +layout(location = 0) out vec4 fragColor; + +void main() { + if ((((fragmentPos.x < fragmentClipPos.x) || (fragmentPos.y < fragmentClipPos.y)) || (fragmentClipPos.x + fragmentClipSize.x < fragmentPos.x)) || (fragmentClipPos.y + fragmentClipSize.y < fragmentPos.y)) { + discard; + } else if (0.0 <= float(fragmentMaskUv.x)) { + vec4 base = texture(atlasSampler, fragmentUv); + float maskR = (texture(atlasSampler, fragmentMaskUv)).r; + fragColor = vec4(base.rgb * mix(vec3(1.0, 1.0, 1.0), fragmentColor.rgb, maskR), base.a * fragmentColor.a); + } else { + fragColor = texture(atlasSampler, fragmentUv) * fragmentColor; + } +} diff --git a/src/silky/drawers/shaders/silky.frag.hlsl b/src/silky/drawers/shaders/silky.frag.hlsl new file mode 100644 index 0000000..14789c8 --- /dev/null +++ b/src/silky/drawers/shaders/silky.frag.hlsl @@ -0,0 +1,36 @@ +// target hlsl dx12 +// from silkyFrag + +Texture2D atlasSampler : register(t0); +SamplerState atlasSamplerSampler : register(s0); + + +struct PSInput { + float4 pos : SV_POSITION; + float2 fragmentUv : TEXCOORD0; + float4 fragmentColor : COLOR0; + float2 fragmentClipPos : TEXCOORD1; + float2 fragmentClipSize : TEXCOORD2; + float2 fragmentPos : TEXCOORD3; + float2 fragmentMaskUv : TEXCOORD4; +}; + +float4 PSMain(PSInput input) : SV_TARGET { + float2 fragmentUv = input.fragmentUv; + float4 fragmentColor = input.fragmentColor; + float2 fragmentClipPos = input.fragmentClipPos; + float2 fragmentClipSize = input.fragmentClipSize; + float2 fragmentPos = input.fragmentPos; + float2 fragmentMaskUv = input.fragmentMaskUv; + float4 fragColor = float4(0.0, 0.0, 0.0, 0.0); + if ((((fragmentPos.x < fragmentClipPos.x) || (fragmentPos.y < fragmentClipPos.y)) || (fragmentClipPos.x + fragmentClipSize.x < fragmentPos.x)) || (fragmentClipPos.y + fragmentClipSize.y < fragmentPos.y)) { + discard; + } else if (0.0 <= float(fragmentMaskUv.x)) { + float4 base = atlasSampler.Sample(atlasSamplerSampler, fragmentUv); + float maskR = (atlasSampler.Sample(atlasSamplerSampler, fragmentMaskUv)).r; + fragColor = float4(base.rgb * lerp(float3(1.0, 1.0, 1.0), fragmentColor.rgb, maskR), base.a * fragmentColor.a); + } else { + fragColor = atlasSampler.Sample(atlasSamplerSampler, fragmentUv) * fragmentColor; + } + return fragColor; +} diff --git a/src/silky/drawers/shaders/silky.frag.spv b/src/silky/drawers/shaders/silky.frag.spv index b669e53..6afcc4e 100644 Binary files a/src/silky/drawers/shaders/silky.frag.spv and b/src/silky/drawers/shaders/silky.frag.spv differ diff --git a/src/silky/drawers/shaders/silky.ps.cso b/src/silky/drawers/shaders/silky.ps.cso new file mode 100644 index 0000000..61896d7 Binary files /dev/null and b/src/silky/drawers/shaders/silky.ps.cso differ diff --git a/src/silky/drawers/shaders/silky.vert b/src/silky/drawers/shaders/silky.vert index 7ecff2b..4d81392 100644 --- a/src/silky/drawers/shaders/silky.vert +++ b/src/silky/drawers/shaders/silky.vert @@ -1,33 +1,34 @@ -#version 450 - -layout(location = 0) in vec2 inPos; -layout(location = 1) in vec2 inUv; -layout(location = 2) in vec4 inColor; -layout(location = 3) in vec2 inClipPos; -layout(location = 4) in vec2 inClipSize; -layout(location = 5) in vec2 inMaskUv; - -layout(location = 0) out vec2 fragUv; -layout(location = 1) out vec4 fragColor; -layout(location = 2) out vec2 fragClipPos; -layout(location = 3) out vec2 fragClipSize; -layout(location = 4) out vec2 fragPos; -layout(location = 5) out vec2 fragMaskUv; - -layout(push_constant) uniform PushConstants { - vec2 viewportSize; -} pc; - -void main() { - gl_Position = vec4(inPos, 0.0, 1.0); - fragUv = inUv; - fragColor = inColor; - fragClipPos = inClipPos; - fragClipSize = inClipSize; - fragMaskUv = inMaskUv; - // Recover pixel position from Vulkan NDC (-1 top, +1 bottom) - fragPos = vec2( - (inPos.x * 0.5 + 0.5) * pc.viewportSize.x, - (inPos.y * 0.5 + 0.5) * pc.viewportSize.y - ); -} +#version 450 +// from silkyVert + +layout(push_constant) uniform ShadyPushConstants { + vec2 viewportSize; + vec2 atlasSize; +} shadyPushConstants; +#define viewportSize shadyPushConstants.viewportSize +#define atlasSize shadyPushConstants.atlasSize + +layout(location = 0) in vec2 pos; +layout(location = 1) in vec2 uv; +layout(location = 2) in vec4 color; +layout(location = 3) in vec2 clipPos; +layout(location = 4) in vec2 clipSize; +layout(location = 5) in vec2 maskUv; +layout(location = 0) out vec2 fragmentUv; +layout(location = 1) out vec4 fragmentColor; +layout(location = 2) out vec2 fragmentClipPos; +layout(location = 3) out vec2 fragmentClipSize; +layout(location = 4) out vec2 fragmentPos; +layout(location = 5) out vec2 fragmentMaskUv; + +void main() { + vec2 ndc = vec2((pos.x / viewportSize.x) * 2.0 - 1.0, 1.0 - (pos.y / viewportSize.y) * 2.0); + gl_Position = vec4(ndc.x, ndc.y, 0.0, 1.0); + fragmentUv = uv / atlasSize; + fragmentColor = vec4(color); + fragmentClipPos = clipPos; + fragmentClipSize = clipSize; + fragmentPos = pos; + fragmentMaskUv = maskUv / atlasSize; + gl_Position.y = -gl_Position.y; +} diff --git a/src/silky/drawers/shaders/silky.vert.hlsl b/src/silky/drawers/shaders/silky.vert.hlsl new file mode 100644 index 0000000..71e4317 --- /dev/null +++ b/src/silky/drawers/shaders/silky.vert.hlsl @@ -0,0 +1,52 @@ +// target hlsl dx12 +// from silkyVert + +cbuffer ShadyUniforms0 : register(b0) { + float2 viewportSize; + float2 atlasSize; +}; + + +struct VSOutput { + float4 pos : SV_POSITION; + float2 fragmentUv : TEXCOORD0; + float4 fragmentColor : COLOR0; + float2 fragmentClipPos : TEXCOORD1; + float2 fragmentClipSize : TEXCOORD2; + float2 fragmentPos : TEXCOORD3; + float2 fragmentMaskUv : TEXCOORD4; +}; + +VSOutput VSMain( + float2 pos : POSITION0, + float2 uv : TEXCOORD0, + float4 color : COLOR0, + float2 clipPos : TEXCOORD1, + float2 clipSize : TEXCOORD2, + float2 maskUv : TEXCOORD3 +) { + VSOutput output; + float4 gl_Position = float4(0.0, 0.0, 0.0, 0.0); + float2 fragmentUv = float2(0.0, 0.0); + float4 fragmentColor = float4(0.0, 0.0, 0.0, 0.0); + float2 fragmentClipPos = float2(0.0, 0.0); + float2 fragmentClipSize = float2(0.0, 0.0); + float2 fragmentPos = float2(0.0, 0.0); + float2 fragmentMaskUv = float2(0.0, 0.0); + float2 ndc = float2((pos.x / viewportSize.x) * 2.0 - 1.0, 1.0 - (pos.y / viewportSize.y) * 2.0); + gl_Position = float4(ndc.x, ndc.y, 0.0, 1.0); + fragmentUv = uv / atlasSize; + fragmentColor = float4(color); + fragmentClipPos = clipPos; + fragmentClipSize = clipSize; + fragmentPos = pos; + fragmentMaskUv = maskUv / atlasSize; + output.pos = gl_Position; + output.fragmentUv = fragmentUv; + output.fragmentColor = fragmentColor; + output.fragmentClipPos = fragmentClipPos; + output.fragmentClipSize = fragmentClipSize; + output.fragmentPos = fragmentPos; + output.fragmentMaskUv = fragmentMaskUv; + return output; +} diff --git a/src/silky/drawers/shaders/silky.vert.spv b/src/silky/drawers/shaders/silky.vert.spv index 91021ce..371c782 100644 Binary files a/src/silky/drawers/shaders/silky.vert.spv and b/src/silky/drawers/shaders/silky.vert.spv differ diff --git a/src/silky/drawers/shaders/silky.vs.cso b/src/silky/drawers/shaders/silky.vs.cso new file mode 100644 index 0000000..25c807a Binary files /dev/null and b/src/silky/drawers/shaders/silky.vs.cso differ diff --git a/src/silky/drawers/vk14.nim b/src/silky/drawers/vk14.nim index ef26710..28b136b 100644 --- a/src/silky/drawers/vk14.nim +++ b/src/silky/drawers/vk14.nim @@ -1,12 +1,26 @@ when not defined(windows): {.error: "The Silky Vulkan backend currently requires Windows.".} -import - pixie, vmath, windy -import pkg/vk14 except Window - -const - InitialVertexCapacity = 4096 +import + pixie, vmath, windy +import pkg/vk14 except Window + +when not defined(shadyBinaryShaders): + import std/os, shady, silky/drawers/shader + +const + InitialVertexCapacity = 4096 + +when not defined(shadyBinaryShaders): + const + ShaderDir = currentSourcePath().parentDir / "shaders" + VertexGlslPath = ShaderDir / "silky.vert" + FragmentGlslPath = ShaderDir / "silky.frag" + VertexSpvPath = ShaderDir / "silky.vert.spv" + FragmentSpvPath = ShaderDir / "silky.frag.spv" + +const + ShaderUniformFloatCount = 4 type DrawerVertex* {.packed.} = object @@ -50,24 +64,7 @@ type proc clampViewport(size: IVec2): IVec2 = ivec2(max(1'i32, size.x), max(1'i32, size.y)) -proc normalizeVertices( - vertices: var seq[DrawerVertex], - viewportSize: IVec2, - atlasSize: Vec2 -) = - let - width = max(1.0'f, viewportSize.x.float32) - height = max(1.0'f, viewportSize.y.float32) - for i in 0 ..< vertices.len: - let p = vertices[i].pos - vertices[i].pos = vec2( - (p.x / width) * 2.0'f - 1.0'f, - (p.y / height) * 2.0'f - 1.0'f - ) - vertices[i].uv = vertices[i].uv / atlasSize - vertices[i].maskUv = vertices[i].maskUv / atlasSize - -proc requiresSwapChainRecreate(vkResult: VkResult): bool = +proc requiresSwapChainRecreate(vkResult: VkResult): bool = let code = vkResult.int32 code == VK_SUBOPTIMAL_KHR.int32 or code == VK_ERROR_OUT_OF_DATE_KHR.int32 @@ -445,10 +442,25 @@ proc createRenderPass(ctx: VulkanContext, renderer: var VkRenderer) = ctx.device, renderPassInfo.addr, nil, renderer.renderPass.addr), "Creating render pass") -proc createGraphicsPipeline(ctx: VulkanContext, renderer: var VkRenderer) = - const - vertShaderCode = staticRead("shaders/silky.vert.spv") - fragShaderCode = staticRead("shaders/silky.frag.spv") +proc createGraphicsPipeline(ctx: VulkanContext, renderer: var VkRenderer) = + when defined(shadyBinaryShaders): + const + vertShaderCode = staticRead("shaders/silky.vert.spv") + fragShaderCode = staticRead("shaders/silky.frag.spv") + else: + const + vertShaderCode = compileSpirvShader( + toShader(silkyVert, vulkanGlsl450, shaderVertex), + VertexGlslPath, + VertexSpvPath, + binaryVertex + ) + fragShaderCode = compileSpirvShader( + toShader(silkyFrag, vulkanGlsl450, shaderFragment), + FragmentGlslPath, + FragmentSpvPath, + binaryFragment + ) let vertModule = createShaderModule(ctx.device, vertShaderCode) fragModule = createShaderModule(ctx.device, fragShaderCode) @@ -539,10 +551,10 @@ proc createGraphicsPipeline(ctx: VulkanContext, renderer: var VkRenderer) = pAttachments: colorBlendAttachment.addr, blendConstants: [0.0'f32, 0.0'f32, 0.0'f32, 0.0'f32]) - pushConstantRange = VkPushConstantRange( - stageFlags: VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), - offset: 0, - size: uint32(sizeof(Vec2))) + pushConstantRange = VkPushConstantRange( + stageFlags: VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), + offset: 0, + size: uint32(ShaderUniformFloatCount * sizeof(float32))) pipelineLayoutInfo = VkPipelineLayoutCreateInfo( sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, setLayoutCount: 1, @@ -737,11 +749,10 @@ proc endFrame*( var vertices = newSeqOfCap[DrawerVertex](vertexCount) let quadsArr = cast[ptr UncheckedArray[DrawerVertex]](quads) - for i in 0 ..< quadCount: - vertices.add(quadsArr[i]) - vertices.normalizeVertices(drawer.viewportSize, atlasSize) - - if vertexCount > 0: + for i in 0 ..< quadCount: + vertices.add(quadsArr[i]) + + if vertexCount > 0: copyMem(drawer.renderer.vertexBufferPtr, unsafeAddr vertices[0], vertexCount * sizeof(DrawerVertex)) @@ -783,9 +794,13 @@ proc endFrame*( var vertexBuffers = [drawer.renderer.vertexBuffer] var offsets = [VkDeviceSize(0)] - var descriptorSet = drawer.renderer.descriptorSet - var viewportSizePush = vec2( - safeSize.x.float32, safeSize.y.float32) + var descriptorSet = drawer.renderer.descriptorSet + var shaderUniforms = [ + safeSize.x.float32, + safeSize.y.float32, + atlasSize.x, + atlasSize.y + ] vkCmdBeginRenderPass( commandBuffer, renderPassInfo.addr, VK_SUBPASS_CONTENTS_INLINE) @@ -796,10 +811,11 @@ proc endFrame*( commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, drawer.renderer.pipelineLayout, 0, 1, descriptorSet.addr, 0, nil) - vkCmdPushConstants( - commandBuffer, drawer.renderer.pipelineLayout, - VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), - 0, uint32(sizeof(Vec2)), viewportSizePush.addr) + vkCmdPushConstants( + commandBuffer, drawer.renderer.pipelineLayout, + VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), + 0, uint32(ShaderUniformFloatCount * sizeof(float32)), + shaderUniforms.addr) vkCmdBindVertexBuffers( commandBuffer, 0, 1, vertexBuffers[0].addr, offsets[0].addr) if vertexCount > 0: diff --git a/tests/test_directx_shader_runtime.nim b/tests/test_directx_shader_runtime.nim new file mode 100644 index 0000000..f9ec007 --- /dev/null +++ b/tests/test_directx_shader_runtime.nim @@ -0,0 +1,16 @@ +when defined(windows) and defined(useDirectX) and defined(shadyRunDx12): + import silky, vmath + + let window = newWindow( + title = "Silky Shady DirectX shader test", + size = ivec2(64, 64), + visible = false + ) + let atlasBuilder = newAtlasBuilder(32, 1) + + discard newSilky(window, atlasBuilder.atlasImage, atlasBuilder.atlas) + echo "Silky DirectX Shady shader runtime test passed" +elif defined(windows) and defined(useDirectX): + echo "Silky DirectX Shady shader runtime test skipped; run with -d:shadyRunDx12" +else: + echo "Silky DirectX Shady shader runtime test skipped; Windows DirectX is required" diff --git a/tests/test_shader_codegen.nim b/tests/test_shader_codegen.nim new file mode 100644 index 0000000..8f1a9a1 --- /dev/null +++ b/tests/test_shader_codegen.nim @@ -0,0 +1,55 @@ +import strutils, shady, silky/drawers/shader + +block: + let vertex = toShader(silkyVert, glsl3Desktop, shaderVertex) + doAssert "uniform vec2 viewportSize;" in vertex + doAssert "uniform vec2 atlasSize;" in vertex + doAssert "in vec2 pos;" in vertex + doAssert "in vec2 maskUv;" in vertex + doAssert "out vec2 fragmentUv;" in vertex + doAssert "out vec2 fragmentMaskUv;" in vertex + + let fragment = toShader(silkyFrag, glsl3Desktop, shaderFragment) + doAssert "uniform sampler2D atlasSampler;" in fragment + doAssert "discard" in fragment + doAssert "texture(atlasSampler, fragmentUv)" in fragment + doAssert "(texture(atlasSampler, fragmentMaskUv)).r" in fragment + doAssert "mix(vec3(1.0, 1.0, 1.0), fragmentColor.rgb, maskR)" in fragment + +block: + let vertex = toShader(silkyVert, vulkanGlsl450, shaderVertex) + doAssert "#version 450" in vertex + doAssert "layout(push_constant) uniform ShadyPushConstants" in vertex + doAssert "vec2 viewportSize;" in vertex + doAssert "vec2 atlasSize;" in vertex + doAssert "layout(location = 0) in vec2 pos;" in vertex + doAssert "layout(location = 5) in vec2 maskUv;" in vertex + doAssert "layout(location = 5) out vec2 fragmentMaskUv;" in vertex + doAssert "gl_Position.y = -gl_Position.y;" in vertex + + let fragment = toShader(silkyFrag, vulkanGlsl450, shaderFragment) + doAssert "layout(set = 0, binding = 0) uniform sampler2D atlasSampler;" in fragment + doAssert "layout(location = 5) in vec2 fragmentMaskUv;" in fragment + doAssert "layout(location = 0) out vec4 fragColor;" in fragment + doAssert "(texture(atlasSampler, fragmentMaskUv)).r" in fragment + +block: + let vertex = toHLSL(silkyVert, shaderVertex) + doAssert "cbuffer ShadyUniforms0 : register(b0)" in vertex + doAssert "float2 viewportSize;" in vertex + doAssert "float2 atlasSize;" in vertex + doAssert "float2 pos : POSITION0" in vertex + doAssert "float2 maskUv : TEXCOORD3" in vertex + doAssert "float4 pos : SV_POSITION;" in vertex + + let fragment = toHLSL(silkyFrag, shaderFragment) + doAssert "Texture2D atlasSampler : register(t0);" in fragment + doAssert "SamplerState atlasSamplerSampler : register(s0);" in fragment + doAssert "struct PSInput" in fragment + doAssert "float4 pos : SV_POSITION;" in fragment + doAssert "float4 PSMain(PSInput input) : SV_TARGET" in fragment + doAssert "atlasSampler.Sample(atlasSamplerSampler, fragmentUv)" in fragment + doAssert "(atlasSampler.Sample(atlasSamplerSampler, fragmentMaskUv)).r" in fragment + doAssert "lerp(float3(1.0, 1.0, 1.0), fragmentColor.rgb, maskR)" in fragment + +echo "Silky Shady shader codegen tests passed"