diff --git a/engine/papyrus/src/DrawCommand.zig b/engine/papyrus/src/DrawCommand.zig index d0500af..aa5f78b 100644 --- a/engine/papyrus/src/DrawCommand.zig +++ b/engine/papyrus/src/DrawCommand.zig @@ -31,7 +31,8 @@ pub const PrimitiveText = struct { text: papyrus.LocText, // unsafe for multithreading color: papyrus.Color, textSize: f32, - rendererHash: u32, + //rendererHash: u32, + fontHandle: u32, // this is just a core.Name handle flags: packed struct { setSourceGeometry: bool, }, diff --git a/engine/papyrus/src/Font.zig b/engine/papyrus/src/Font.zig index 5329f92..944b994 100644 --- a/engine/papyrus/src/Font.zig +++ b/engine/papyrus/src/Font.zig @@ -40,7 +40,8 @@ pub const FontAtlas = struct { fromArchive: bool = false, filePath: []const u8, - rendererHash: u32 = 0, // optional field to associate this atlas with an identifier to the renderer implementation + // rendererHash: u32 = 0, // optional field to associate this atlas with an identifier to the renderer implementation + fontHandle: u32 = 0, isEmbedded: bool = false, isSDF: bool = false, @@ -102,6 +103,10 @@ pub const FontAtlas = struct { var rv = try initFromArchive(allocator, archiveBytes); rv.fromArchive = true; + + var fontAsName = core.MakeName(fontName); + rv.fontHandle = fontAsName.handle(); + return rv; } diff --git a/engine/papyrus/src/papyrus.zig b/engine/papyrus/src/papyrus.zig index 3b0a03d..dbd0380 100644 --- a/engine/papyrus/src/papyrus.zig +++ b/engine/papyrus/src/papyrus.zig @@ -351,6 +351,8 @@ pub const PapyrusRuntime = struct { allocator: std.mem.Allocator, fontCache: *FontCache, + var gPapyrusRuntime: *PapyrusRuntime = undefined; + pub fn create(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); @@ -359,6 +361,8 @@ pub const PapyrusRuntime = struct { .fontCache = try FontCache.create(allocator), }; + gPapyrusRuntime = self; + return self; } @@ -368,6 +372,17 @@ pub const PapyrusRuntime = struct { return rv; } + pub fn get() *@This() { + return gPapyrusRuntime; + } + + pub fn getFont(self: *@This(), nameHandle: u32) ?*FontAtlas { + if (self.fontCache.fonts.get(nameHandle)) |font| { + return font.atlas; + } + return null; + } + pub fn destroy(self: *@This()) void { self.fontCache.destroy(); self.allocator.destroy(self); @@ -1082,20 +1097,24 @@ pub const Context = struct { }, } }); - try drawList.append(.{ .node = node, .primitive = .{ - .Text = .{ - .tl = dlb.resolvedPos.add(.{ .x = 3 + 5, .y = 3 }), - .size = .{ .x = dlb.resolvedSize.x, .y = panel.titleSize }, - .text = n.text, - .renderMode = n.textMode, - .color = panel.titleColor, - .textSize = panel.titleSize - 4, - .rendererHash = panel.font.rendererHash, - .flags = .{ - .setSourceGeometry = false, + try drawList.append(.{ + .node = node, + .primitive = .{ + .Text = .{ + .tl = dlb.resolvedPos.add(.{ .x = 3 + 5, .y = 3 }), + .size = .{ .x = dlb.resolvedSize.x, .y = panel.titleSize }, + .text = n.text, + .renderMode = n.textMode, + .color = panel.titleColor, + .textSize = panel.titleSize - 4, + // .rendererHash = panel.font.rendererHash, + .fontHandle = panel.font.fontHandle, + .flags = .{ + .setSourceGeometry = false, + }, }, }, - } }); + }); self._displayLayout.items[node.index] = .{ .baseSize = n.baseSize, @@ -1131,20 +1150,24 @@ pub const Context = struct { } }, .DisplayText => |txt| { - try drawList.append(.{ .node = node, .primitive = .{ - .Text = .{ - .tl = dlb.resolvedPos, - .size = n.size, - .text = n.text, - .renderMode = n.textMode, - .color = n.style.foregroundColor, - .textSize = txt.textSize, - .rendererHash = txt.font.atlas.rendererHash, - .flags = .{ - .setSourceGeometry = false, + try drawList.append(.{ + .node = node, + .primitive = .{ + .Text = .{ + .tl = dlb.resolvedPos, + .size = n.size, + .text = n.text, + .renderMode = n.textMode, + .color = n.style.foregroundColor, + .textSize = txt.textSize, + .fontHandle = txt.font.atlas.fontHandle, + // .rendererHash = txt.font.atlas.rendererHash, + .flags = .{ + .setSourceGeometry = false, + }, }, }, - } }); + }); self._displayLayout.items[node.index] = .{ .baseSize = n.baseSize, @@ -1184,7 +1207,7 @@ pub const Context = struct { var yOffset: f32 = sizePerLine; const width = defaultHeight / 2 * 120; - const fontHash = self.fontCache.defaultMonoFont.atlas.rendererHash; + const fontHandle = self.fontCache.defaultMonoFont.atlas.fontHandle; try self.mousePick.addMousePickInfo(&self, drawList); @@ -1235,7 +1258,7 @@ pub const Context = struct { .renderMode = .NoControl, .color = Color.Yellow, .textSize = defaultHeight, - .rendererHash = fontHash, + .fontHandle = fontHandle, .flags = .{ .setSourceGeometry = false, }, diff --git a/engine/papyrus/src/primitives/button.zig b/engine/papyrus/src/primitives/button.zig index e7d7de3..a5d9f20 100644 --- a/engine/papyrus/src/primitives/button.zig +++ b/engine/papyrus/src/primitives/button.zig @@ -92,7 +92,8 @@ pub fn addToDrawList(dlb: DrawListBuilder) !void { .text = dlb.n.text, .renderMode = dlb.n.textMode, .textSize = button.textSize, - .rendererHash = button.font.atlas.rendererHash, + .fontHandle = button.font.atlas.fontHandle, + // .rendererHash = button.font.atlas.rendererHash, // TODO: figure out centering. .flags = .{ .setSourceGeometry = false, diff --git a/engine/papyrus/src/primitives/textEntry.zig b/engine/papyrus/src/primitives/textEntry.zig index 0c62c50..a2891ab 100644 --- a/engine/papyrus/src/primitives/textEntry.zig +++ b/engine/papyrus/src/primitives/textEntry.zig @@ -70,18 +70,21 @@ pub fn addToDrawList(dlb: DrawListBuilder) !void { try drawlist.append(.{ .node = dlb.node, - .primitive = .{ .Text = .{ - .color = foregroundColor, - .tl = dlb.resolvedPos.add(.{ .x = 5, .y = 2 }), - .size = dlb.resolvedSize, - .renderMode = dlb.n.textMode, - .textSize = te.textSize, - .text = LocText.fromUtf8(te.editText.items), - .rendererHash = te.font.atlas.rendererHash, - .flags = .{ - .setSourceGeometry = (te.entryState == .Pressed), + .primitive = .{ + .Text = .{ + .color = foregroundColor, + .tl = dlb.resolvedPos.add(.{ .x = 5, .y = 2 }), + .size = dlb.resolvedSize, + .renderMode = dlb.n.textMode, + .textSize = te.textSize, + .text = LocText.fromUtf8(te.editText.items), + .fontHandle = te.font.atlas.fontHandle, + //.rendererHash = te.font.atlas.rendererHash, + .flags = .{ + .setSourceGeometry = (te.entryState == .Pressed), + }, }, - } }, + }, }); // someday i need to re-implement this layout algorithm diff --git a/engine/papyrus/src/textRender/textRenderGeometry.zig b/engine/papyrus/src/textRender/textRenderGeometry.zig index 7e2f421..3154459 100644 --- a/engine/papyrus/src/textRender/textRenderGeometry.zig +++ b/engine/papyrus/src/textRender/textRenderGeometry.zig @@ -212,7 +212,7 @@ pub fn resetAllLines(self: *@This()) !void { // get yourself a new GeometryLineEntry pub fn recycleOrNewGeoLine(self: *@This()) GeometryLineEntry { if (self.geoPool.items.len > 0) { - const newLine = self.geoPool.pop(); + const newLine = self.geoPool.pop().?; return .{ .yOffset = 0, .lineGeo = newLine, diff --git a/engine/rend/src/rend.zig b/engine/rend/src/rend.zig index e9fd61a..ee0c1bd 100644 --- a/engine/rend/src/rend.zig +++ b/engine/rend/src/rend.zig @@ -18,6 +18,7 @@ pub const gltfLoader = @import("meshes/gltfLoader.zig"); // switch out based on backend implementation pub const renderer = @import("sgpu/renderer.zig"); pub const context = renderer.context; // context getter func +pub const Renderer = renderer.Renderer; pub const MeshPool = renderer.MeshPool; diff --git a/engine/rend/src/sgpu/TextureList.zig b/engine/rend/src/sgpu/TextureList.zig index 46cc3a9..2478a89 100644 --- a/engine/rend/src/sgpu/TextureList.zig +++ b/engine/rend/src/sgpu/TextureList.zig @@ -177,6 +177,88 @@ pub fn getMiplevelFromSize(size: core.Vector2u) u32 { return std.math.log2(@as(u32, @intCast(@max(size.x, size.y)))) + 1; } +pub const UploadTextureBytesOptions = struct { + bytes: []const u8, + size: core.Vector2u, + use_blocky: bool = false, +}; + +pub fn uploadTextureFromBytes(self: *@This(), name: *core.Name, opts: UploadTextureBytesOptions) !*Texture { + const cmd = self.device.acquireGPUCommandBuffer(); + const mipLevelCount = getMiplevelFromSize(opts.size); + + core.engine_log("creating mipmap level {d}", .{mipLevelCount}); + + const gpuTexture = self.device.createGPUTexture(&std.mem.zeroInit(gpu.GPUTextureCreateInfo, .{ + .type = .texturetype2d, + .format = .textureformatR8g8b8a8UnormSrgb, + .usage = .{ .textureusageSampler = true, .textureusageColorTarget = true }, + .width = opts.size.x, + .height = opts.size.y, + .layer_count_or_depth = 1, + .num_levels = mipLevelCount, + })); + + const transferBuffer = self.device.createGPUTransferBuffer(&.{ + .usage = .transferbufferusageUpload, + .size = opts.size.x * opts.size.y * @sizeOf(u32), + .props = 0, + }); + + { + const data: [*]u8 = @ptrCast(self.device.mapGPUTransferBuffer(transferBuffer, false)); + for (opts.bytes, 0..) |pixel, i| { + data[i] = pixel; + } + } + + self.device.unmapGPUTransferBuffer(transferBuffer); + + const copyPass = cmd.beginGPUCopyPass(); + // copyPass.uploadToGPUTexture(source: [*c]const GPUTextureTransferInfo, destination: [*c]const GPUTextureRegion, cycle: bool) + copyPass.uploadToGPUTexture( + &.{ + .transfer_buffer = transferBuffer, + .offset = 0, + .pixels_per_row = opts.size.x, + .rows_per_layer = opts.size.y, + }, + &std.mem.zeroInit(gpu.GPUTextureRegion, .{ + .texture = gpuTexture, + .w = opts.size.x, + .h = opts.size.y, + .d = 1, + }), + false, + ); + + copyPass.endGPUCopyPass(); + + if (mipLevelCount > 1) + cmd.generateMipmapsForGPUTexture(gpuTexture); + + if (!cmd.submitGPUCommandBuffer()) { + return error.CopyFailed; + } + self.device.releaseGPUTransferBuffer(transferBuffer); + + const tex = try self.allocator.create(Texture); + tex.* = .{ + .texture = gpuTexture, + .id = 0, + .format = .f32_rgba, + .usage = .{}, + .size = opts.size, + .name = name.*, + }; + + tex.samplerMode = if (opts.use_blocky) .blocky else .linear; + + self.map.put(self.allocator, name.handle(), tex) catch return error.UnableToLoad; + + return tex; +} + pub fn uploadTextureFromPath(self: *@This(), name: core.Name, path: []const u8) !*Texture { var png = try core.png.PngContents.initFromFS(core.fs(), self.allocator, path); defer png.deinit(); diff --git a/engine/rend/src/sgpu/renderer.zig b/engine/rend/src/sgpu/renderer.zig index 665d5f2..f5464a8 100644 --- a/engine/rend/src/sgpu/renderer.zig +++ b/engine/rend/src/sgpu/renderer.zig @@ -1057,6 +1057,8 @@ pub fn textureExists(name: []const u8) bool { return context().textureList.requestMap.contains(n.handle()); } +pub const addVertexAttributesFromStruct = @import("vertexAttributes.zig").addVertexAttributesFromStruct; + pub const CustomMeshRenderFunc = *const fn (?*anyopaque) void; pub const GPUTextureType = gpu.GPUTexture; diff --git a/engine/rend/src/sgpu/vertexAttributes.zig b/engine/rend/src/sgpu/vertexAttributes.zig new file mode 100644 index 0000000..31526a8 --- /dev/null +++ b/engine/rend/src/sgpu/vertexAttributes.zig @@ -0,0 +1,64 @@ +const shaderTypes = sdl3.shaderTypes; + +pub fn getVertexFormatFromType(comptime T: type) gpu.GPUVertexElementFormat { + switch (T) { + f32 => { + return gpu.GPUVertexElementFormat.vertexelementformatFloat; + }, + u32, u8vec4 => { + return gpu.GPUVertexElementFormat.vertexelementformatUint; + }, + vec2 => { + return gpu.GPUVertexElementFormat.vertexelementformatFloat2; + }, + vec3 => { + return gpu.GPUVertexElementFormat.vertexelementformatFloat3; + }, + vec4 => { + return gpu.GPUVertexElementFormat.vertexelementformatFloat4; + }, + else => { + @compileError("unable to get vertex format from subtype" ++ @typeName(T)); + }, + } +} + +pub fn addVertexAttributesFromStruct(comptime T: type, pci: *gpu.GPUGraphicsPipelineCreateInfo) !std.ArrayList(gpu.GPUVertexAttribute) { + const ctx = rend.context(); + var list = std.ArrayList(gpu.GPUVertexAttribute).init(ctx.allocator); + var offset: u32 = 0; + + inline for (@typeInfo(T).@"struct".fields) |field| { + try Renderer.addAttribute(&list, &offset, @sizeOf(field.type), getVertexFormatFromType(field.type)); + } + + pci.vertex_input_state = .{ + .num_vertex_buffers = 1, + .vertex_buffer_descriptions = &[_]gpu.GPUVertexBufferDescription{ + .{ .slot = 0, .pitch = @sizeOf(T), .input_rate = .vertexinputrateVertex, .instance_step_rate = 0 }, + }, + .num_vertex_attributes = @intCast(list.items.len), + .vertex_attributes = @ptrCast(list.items.ptr), + }; + + return list; +} + +pub const int = shaderTypes.int; +pub const uint = shaderTypes.uint; + +pub const vec2 = shaderTypes.vec2; +pub const u8vec4 = shaderTypes.u8vec4; +pub const vec3 = shaderTypes.vec3; +pub const vec4 = shaderTypes.vec4; +pub const mat4 = shaderTypes.mat4; +pub const float = shaderTypes.float; + +const std = @import("std"); +const assets = @import("assets"); +const core = @import("core"); +const platform = @import("platform"); +const rend = @import("../rend.zig"); +const Renderer = rend.renderer.Renderer; +const sdl3 = @import("sdl3"); +pub const gpu = sdl3.gpu; diff --git a/engine/sys/src/systemProc.zig b/engine/sys/src/systemProc.zig index b2a0cdd..b64c84d 100644 --- a/engine/sys/src/systemProc.zig +++ b/engine/sys/src/systemProc.zig @@ -10,9 +10,13 @@ fn noprint(comptime fmt: []const u8, args: anytype) void { _ = args; } -const debugPrint = noprint; +const debugPrint = __debugPrint; pub const SubprocessTask = struct { + // any and all string values added to this subprocesstask must exist and be usable across threads. + // i reeccomend converting strings to a name via core.MakeName before passing them over. if they are freed + // by the time the thread uses it, it will crash . + mutex: std.Thread.Mutex = .{}, allocator: std.mem.Allocator, args: []const []const u8, @@ -36,16 +40,21 @@ pub const SubprocessTask = struct { self.stderr = .{}; self.stdout = .{}; debugPrint("running task", .{}); + for (self.args) |arg| { + debugPrint("{s}", .{arg}); + } + + debugPrint("cwd = {s}", .{self.workingDir.?}); self.child = std.process.Child.init(self.args, self.allocator); self.child.?.stdout_behavior = .Inherit; self.child.?.stderr_behavior = .Inherit; self.child.?.cwd = self.workingDir; - _ = self.child.?.spawn() catch {}; + _ = self.child.?.spawn() catch unreachable; self.mutex.unlock(); // close and cleanup everything - self.waitInner() catch unreachable; + self.waitInner() catch {}; } }; diff --git a/engine/ui/shaders/text.frag.hlsl b/engine/ui/shaders/text.frag.hlsl index cde3981..f540a38 100644 --- a/engine/ui/shaders/text.frag.hlsl +++ b/engine/ui/shaders/text.frag.hlsl @@ -25,7 +25,7 @@ float4 main( if(!scissor(pixelPosition, position, size)) { - discard; + // discard; } float4 textColor = float4( @@ -34,7 +34,7 @@ float4 main( getBlueFromUint(Color), getAlphaFromUint(Color)); - if(isSdf == 1) + if(isSdf == 0) { float dist = tex.r; float width = fwidth(dist); @@ -70,12 +70,20 @@ float4 main( //outFragColor = vec4(1.0, 0.0, 0.0, 1.0); } - /* debug test. - if(!rect(pixelPosition, position, size)) - { - outFragColor = vec4(1.0, 0.0, 0.0, 1.0); - } - */ +#if 0 + float4 outFragColor = 0; + + //if(!rect(pixelPosition, position, size)) + //{ + //} + outFragColor = float4(1.0, 0.0, 0.0, 1.0); + + return outFragColor + o * 0.00001; +#else + float alpha = 1.0; + float gray = dot(textColor.xyz, float3(0.2126, 0.7152, 0.0722)); + o = float4(textColor.xyz, tex.x);//pow(tex.x / gray, 1/(2.2)) );//textColor.* alpha); + return float4(1.0, 1.0, 1.0, tex.x) + o * 0.00001; +#endif - return o; } diff --git a/engine/ui/shaders/text.frag.json b/engine/ui/shaders/text.frag.json index 0a9bb68..efe412b 100644 --- a/engine/ui/shaders/text.frag.json +++ b/engine/ui/shaders/text.frag.json @@ -6,7 +6,7 @@ } ], "types" : { - "_13" : { + "_12" : { "name" : "FontInfo", "members" : [ { @@ -16,32 +16,32 @@ }, { "name" : "size", - "type" : "float", + "type" : "vec2", "offset" : 8 }, { "name" : "isSdf", "type" : "uint", - "offset" : 12 + "offset" : 16 }, { "name" : "pad0", "type" : "uint", - "offset" : 16 + "offset" : 20 }, { "name" : "pad2", "type" : "vec2", - "offset" : 20 + "offset" : 24 } ] }, - "_12" : { + "_11" : { "name" : "type.StructuredBuffer.FontInfo", "members" : [ { "name" : "_m0", - "type" : "_13", + "type" : "_12", "array" : [ 0 ], @@ -65,11 +65,6 @@ "name" : "in.var.TEXCOORD1", "location" : 1 }, - { - "type" : "vec2", - "name" : "in.var.TEXCOORD2", - "location" : 2 - }, { "type" : "uint", "name" : "in.var.TEXCOORD3", @@ -101,7 +96,7 @@ ], "ssbos" : [ { - "type" : "_12", + "type" : "_11", "name" : "fontBuffer", "readonly" : true, "block_size" : 0, diff --git a/engine/ui/shaders/text.vert.hlsl b/engine/ui/shaders/text.vert.hlsl index 58c6a66..bdee309 100644 --- a/engine/ui/shaders/text.vert.hlsl +++ b/engine/ui/shaders/text.vert.hlsl @@ -34,7 +34,9 @@ VSOut main(Input v) float2 t = v.Position + s.position; o.pixelPosition = t; - o.Position = float4((t / Extents) * 2 + float2(-1.0f, -1.0f), 0.0, 1.0); + float4 fp = float4((t / Extents) * 2 + float2(-1.0f, -1.0f), 0.0, 1.0); + fp.y = -fp.y; + o.Position = fp; o.UV = v.UV; o.Color = v.Color; diff --git a/engine/ui/shaders/text.vert.json b/engine/ui/shaders/text.vert.json index eabf67b..fb09e79 100644 --- a/engine/ui/shaders/text.vert.json +++ b/engine/ui/shaders/text.vert.json @@ -16,23 +16,23 @@ }, { "name" : "size", - "type" : "float", + "type" : "vec2", "offset" : 8 }, { "name" : "isSdf", "type" : "uint", - "offset" : 12 + "offset" : 16 }, { "name" : "pad0", "type" : "uint", - "offset" : 16 + "offset" : 20 }, { "name" : "pad2", "type" : "vec2", - "offset" : 20 + "offset" : 24 } ] }, diff --git a/engine/ui/shaders/text_shared.hlsli b/engine/ui/shaders/text_shared.hlsli index 554e7a5..3ae2d39 100644 --- a/engine/ui/shaders/text_shared.hlsli +++ b/engine/ui/shaders/text_shared.hlsli @@ -1,6 +1,6 @@ struct FontInfo { float2 position; // 8 bytes alignment 0 - float size; // 8 bytes alignment 8 + float2 size; // 8 bytes alignment 8 uint isSdf; // 4 bytes 16 uint pad0; // 4 bytes float2 pad2; // 8 bytes diff --git a/engine/ui/src/sgpu/papyrusSgpu.zig b/engine/ui/src/sgpu/papyrusSgpu.zig index 06b7aec..0880b65 100644 --- a/engine/ui/src/sgpu/papyrusSgpu.zig +++ b/engine/ui/src/sgpu/papyrusSgpu.zig @@ -21,6 +21,9 @@ rectPipeline: *gpu.GPUGraphicsPipeline = undefined, textPipeline: *gpu.GPUGraphicsPipeline = undefined, tempDrawCommand: std.ArrayListUnmanaged(DrawCommand) = .{}, +fontTextures: std.AutoHashMapUnmanaged(u32, *rend.Texture) = .{}, + +textRenderers: std.AutoHashMapUnmanaged(*papyrus.Context, *TextRenderer) = .{}, const DrawCommand = union(enum(u8)) { rect: struct { @@ -28,6 +31,7 @@ const DrawCommand = union(enum(u8)) { }, text: struct { ssboIndex: u32, + textMeshBuffer: *text_renderer.TextMeshBuffer, }, }; @@ -95,7 +99,7 @@ const ScreenBuffers = struct { pub fn init(device: *gpu.GPUDevice) @This() { return .{ .rect = SsboBuffer.init(device, @sizeOf(rect_frag.Scene), 4096), - .text = undefined, + .text = SsboBuffer.init(device, @sizeOf(text_frag.FontInfo), 4096), }; } }; @@ -117,6 +121,7 @@ pub fn create(allocator: std.mem.Allocator) !*@This() { }; self.screenContext = try self.runtime.addContext(); + try self.textRenderers.put(allocator, self.screenContext, try TextRenderer.create(allocator)); return self; } @@ -132,9 +137,39 @@ pub fn setup(self: *@This()) !void { try self.createTextPipeline(); self.screenBuffers = ScreenBuffers.init(ctx.device); + + const fontCache = papyrus.PapyrusRuntime.get().fontCache; + + // upload and create textures for all the pre-initialized fonts + var iter = fontCache.fonts.valueIterator(); + while (iter.next()) |font| { + const bytes = try font.atlas.makeBitmapRGBA(self.allocator); + defer self.allocator.free(bytes); + + const formattedName = try std.fmt.allocPrint(self.stringArena.allocator(), "papyrusFont:{s}", .{font.name.utf8()}); + + var name = core.MakeName(formattedName); + + const texture = try rend.context().textureList.uploadTextureFromBytes(&name, .{ + .bytes = bytes, + .size = .{ + .x = @intCast(font.atlas.atlasSize.x), + .y = @intCast(font.atlas.atlasSize.y), + }, + }); + try self.fontTextures.put(self.allocator, font.atlas.fontHandle, texture); + } } pub fn destroy(self: *@This()) void { + var iterator = self.textRenderers.valueIterator(); + + while (iterator.next()) |next| { + next.*.destroy(); + } + + self.fontTextures.deinit(self.allocator); + self.textRenderers.deinit(self.allocator); self.screenContext.destroy(); self.stringArena.deinit(); self.runtime.destroy(); @@ -154,7 +189,7 @@ pub fn createTextPipeline(self: *@This()) !void { pci.vertex_shader = vertex; pci.fragment_shader = fragment; - var attributes = try textRenderer.addVertexAttributes(&pci); + var attributes = try text_renderer.addVertexAttributes(&pci); defer attributes.deinit(); pci.target_info.num_color_targets = 1; @@ -162,11 +197,20 @@ pub fn createTextPipeline(self: *@This()) !void { pci.target_info.color_target_descriptions = &[_]gpu.GPUColorTargetDescription{ .{ .format = ctx.swapchainTargetFormat, - .blend_state = std.mem.zeroes(gpu.GPUColorTargetBlendState), + .blend_state = std.mem.zeroInit(gpu.GPUColorTargetBlendState, .{ + .alpha_blend_op = .blendopAdd, + .color_blend_op = .blendopAdd, + .src_color_blendfactor = .blendfactorSrcAlpha, + .dst_color_blendfactor = .blendfactorOneMinusSrcAlpha, + .src_alpha_blendfactor = .blendfactorSrcAlpha, + .dst_alpha_blendfactor = .blendfactorOneMinusSrcAlpha, + .enable_blend = true, + }), }, }; pci.rasterizer_state.fill_mode = .fillmodeFill; + pci.rasterizer_state.cull_mode = .cullmodeNone; self.textPipeline = ctx.device.createGPUGraphicsPipeline(&pci); } @@ -202,6 +246,7 @@ pub fn createRectPipeline(self: *@This()) !void { }; pci.rasterizer_state.fill_mode = .fillmodeFill; + pci.rasterizer_state.cull_mode = .cullmodeNone; self.rectPipeline = ctx.device.createGPUGraphicsPipeline(&pci); } @@ -218,12 +263,35 @@ pub fn postRender(p: *anyopaque, cmd: *gpu.GPUCommandBuffer) void { pub fn onShaderReload(p: *anyopaque) void { const self: *@This() = @ptrCast(@alignCast(p)); self.createRectPipeline() catch return; // you think i give a remote fuck about leaks? this is a debug function son. + self.createTextPipeline() catch return; +} + +pub fn getTextRenderer(self: *@This(), ctx: *papyrus.Context) ?*TextRenderer { + if (self.textRenderers.get(ctx)) |tr| { + return tr; + } + core.engine_err("unable to get text renderer for papyrus context", .{}); + return null; +} + +pub fn getFontSampler(self: *@This(), fontHandle: u32) ?gpu.GPUTextureSamplerBinding { + if (self.fontTextures.get(fontHandle)) |ft| { + const rv = gpu.GPUTextureSamplerBinding{ + .sampler = if (ft.samplerMode == .blocky) rend.context().blockySampler else rend.context().linearSampler, + .texture = ft.texture, + }; + return rv; + } + return null; } pub fn drawToTarget(self: *@This(), cmd: *gpu.GPUCommandBuffer, ctx: *papyrus.Context, targetTexture: *gpu.GPUTexture) void { ctx.makeDrawList(&self.drawList, &self.stringArena) catch return; + const textRenderer = self.getTextRenderer(ctx); + var rectIndex: u32 = 0; + var textIndex: u32 = 0; // map ssbos const renderer = rend.context(); @@ -232,6 +300,7 @@ pub fn drawToTarget(self: *@This(), cmd: *gpu.GPUCommandBuffer, ctx: *papyrus.Co // upload rectangle ssbos { const rectBuffer = self.screenBuffers.rect.mapSlice(rect_frag.Scene, renderer.device); + const textBuffer = self.screenBuffers.text.mapSlice(text_frag.FontInfo, renderer.device); for (self.drawList.items) |item| { switch (item.primitive) { @@ -267,11 +336,39 @@ pub fn drawToTarget(self: *@This(), cmd: *gpu.GPUCommandBuffer, ctx: *papyrus.Co rectIndex += 1; }, - .Text => {}, + .Text => |text| { + if (textRenderer) |tr| { + const node = item.node; + + const meshBuffer = tr.uploadMesh(copyPass, node, text) catch return; + + textBuffer[textIndex] = .{ + .isSdf = if (meshBuffer.isSDF) 1 else 0, + .position = .{ .x = text.tl.x, .y = text.tl.y }, + .size = .{ .x = text.size.x, .y = text.size.y }, + .pad0 = undefined, + .pad2 = undefined, + }; + // textBuffer[textIndex] = .{ + // .. fill this shit out + // }; + + self.tempDrawCommand.append(self.allocator, .{ + .text = .{ + .ssboIndex = textIndex, + .textMeshBuffer = meshBuffer, + }, + }) catch unreachable; + textIndex += 1; + } + }, } } self.screenBuffers.rect.submit(copyPass); self.screenBuffers.rect.unmap(renderer.device); + + self.screenBuffers.text.submit(copyPass); + self.screenBuffers.text.unmap(renderer.device); } copyPass.endGPUCopyPass(); @@ -280,7 +377,15 @@ pub fn drawToTarget(self: *@This(), cmd: *gpu.GPUCommandBuffer, ctx: *papyrus.Co self.first = false; for (self.drawList.items) |item| { - core.engine_log("{any}", .{item}); + //core.engine_log("{any}", .{item}); + switch (item.primitive) { + .Text => |text| { + core.engine_log("text position {d}, {d} size {d}x{d}", .{ text.tl.x, text.tl.y, text.size.x, text.size.y }); + }, + .Rect => |rect| { + core.engine_log("rect position {d}, {d} size {d}x{d}", .{ rect.tl.x, rect.tl.y, rect.size.x, rect.size.y }); + }, + } } } @@ -310,8 +415,6 @@ pub fn drawToTarget(self: *@This(), cmd: *gpu.GPUCommandBuffer, ctx: *papyrus.Co } const pass = cmd.beginGPURenderPass(&targetInfo, 1, null); - pass.bindGPUVertexBuffers(0, &.{ .buffer = rend.context().meshPool.vertexBuffer, .offset = 0 }, 1); - pass.bindGPUIndexBuffer(&.{ .buffer = rend.context().meshPool.indexBuffer, .offset = 0 }, .indexelementsize32bit); pass.bindGPUFragmentSamplers(0, &.{ .sampler = rend.context().blockySampler, .texture = rend.context().defaultTexture.texture }, 1); if (self.quadMesh) |quadMesh| { @@ -322,11 +425,40 @@ pub fn drawToTarget(self: *@This(), cmd: *gpu.GPUCommandBuffer, ctx: *papyrus.Co pass.bindGPUVertexStorageBuffers(0, &self.screenBuffers.rect.buffer, 1); pass.bindGPUFragmentStorageBuffers(0, &self.screenBuffers.rect.buffer, 1); - // bind samplers if needed + pass.bindGPUVertexBuffers(0, &.{ .buffer = rend.context().meshPool.vertexBuffer, .offset = 0 }, 1); + pass.bindGPUIndexBuffer(&.{ .buffer = rend.context().meshPool.indexBuffer, .offset = 0 }, .indexelementsize32bit); + + // todo: bind samplers for textures. + pass.drawGPUIndexedPrimitives(quadMesh.index.size, 1, quadMesh.index.start, @intCast(quadMesh.vertex.start), rect.ssboIndex); }, .text => |text| { - _ = text; + if (textRenderer) |tr| { + const tmb = text.textMeshBuffer; + tr.bindFontBuffersAndSamplers(pass, tmb); + + pass.bindGPUVertexStorageBuffers(0, &self.screenBuffers.rect.buffer, 1); + pass.bindGPUFragmentStorageBuffers(0, &self.screenBuffers.rect.buffer, 1); + pass.bindGPUFragmentSamplers(0, @ptrCast(&self.getFontSampler(text.textMeshBuffer.fontHandle)), 1); + pass.bindGPUGraphicsPipeline(self.textPipeline); + + pass.drawGPUIndexedPrimitives(tmb.indexCount, 1, 0, 0, text.ssboIndex); + + // pass.drawGPUIndexedPrimitives(tr., fewafe, first_index: u32, vertex_offset: i32, first_instance: u32) + // pass.bindGPUVertexBuffers(0, &.{ .buffer = tr.getVertexBuffer(text.meshIndex), .offset = 0 }, 1); + // pass.bindGPUIndexBuffer(&.{ .buffer = tr.getIndexBuffer(text.meshIndex), .offset = 0 }, .indexelementsize32bit); + + // pass.bindGPUFragmentSamplers(0, &tr.getFontSampler(text.meshIndex), 1); + + // // pass.bindGPUFragmentSamplers(0, &.{ + // // .sampler = rend.context().blockySampler, + // // .texture = rend.context().defaultTexture.texture, + // // }, 1); + + // pass.bindGPUVertexStorageBuffers(0, &self.screenBuffers.rect.buffer, 1); + // pass.bindGPUFragmentStorageBuffers(0, &self.screenBuffers.rect.buffer, 1); + // + } }, } } @@ -337,7 +469,8 @@ pub fn drawToTarget(self: *@This(), cmd: *gpu.GPUCommandBuffer, ctx: *papyrus.Co self.tempDrawCommand.clearRetainingCapacity(); } -pub const textRenderer = @import("textRenderer.zig"); +pub const text_renderer = @import("textRenderer.zig"); +pub const TextRenderer = text_renderer.TextRenderer; pub const rect_frag = @import("rect.frag"); pub const rect_vert = @import("rect.vert"); diff --git a/engine/ui/src/sgpu/textRenderer.zig b/engine/ui/src/sgpu/textRenderer.zig index bd91f58..cc9b8c6 100644 --- a/engine/ui/src/sgpu/textRenderer.zig +++ b/engine/ui/src/sgpu/textRenderer.zig @@ -4,44 +4,508 @@ const vec2 = text_frag.vec2; const u8vec4 = text_frag.u8vec4; -const TextMeshVertex = extern struct { +pub const TextMeshVertex = extern struct { position: vec2, uv: vec2, - Color: u8vec4, + color: u8vec4, + // pad0: u32 = 0, hope it isnt needed.. }; pub fn addVertexAttributes(pci: *gpu.GPUGraphicsPipelineCreateInfo) !std.ArrayList(gpu.GPUVertexAttribute) { - const ctx = rend.context(); - - var list = std.ArrayList(gpu.GPUVertexAttribute).init(ctx.allocator); - - var offset: u32 = 0; - { - const Renderer = rend.renderer.Renderer; - try Renderer.addAttribute(&list, &offset, @sizeOf(f32) * 2, .vertexelementformatFloat2); - try Renderer.addAttribute(&list, &offset, @sizeOf(f32) * 2, .vertexelementformatFloat2); - try Renderer.addAttribute(&list, &offset, @sizeOf(u32), .vertexelementformatUint); - } - - pci.vertex_input_state = .{ - .num_vertex_buffers = 1, - .vertex_buffer_descriptions = &[_]gpu.GPUVertexBufferDescription{ - .{ .slot = 0, .pitch = @sizeOf(TextMeshVertex), .input_rate = .vertexinputrateVertex, .instance_step_rate = 0 }, - }, - .num_vertex_attributes = @intCast(list.items.len), - .vertex_attributes = @ptrCast(list.items.ptr), - }; - - return list; + return rend.renderer.addVertexAttributesFromStruct(TextMeshVertex, pci); } -const TextBuffer = struct { - pub fn create() @This() {} +pub fn TextBufferList(comptime T: type) type { + return struct { + slice: []T, + count: u32 = 0, + + pub fn init(s: []T) @This() { + return .{ + .slice = s, + .count = 0, + }; + } + + pub fn push(self: *@This(), v: T) !void { + if (self.count < self.slice.len) { + self.slice[self.count] = v; + self.count += 1; + } else { + return error.OutOfMemory; + } + } + }; +} + +pub fn pushQuadToWriters( + topLeft: core.Vector2f, + size: core.Vector2f, + uvTopLeft: core.Vector2f, + uvSize: core.Vector2f, + color: core.colors.Color, + vertex: *TextBufferList(TextMeshVertex), + index: *TextBufferList(u32), +) !void { + const o = vertex.count; + + const quadColor: u8vec4 = .{ + @intFromFloat(std.math.clamp(color.r, 0.0, 1.0) * 255), + @intFromFloat(std.math.clamp(color.g, 0.0, 1.0) * 255), + @intFromFloat(std.math.clamp(color.b, 0.0, 1.0) * 255), + @intFromFloat(std.math.clamp(color.a, 0.0, 1.0) * 255), + }; + + try vertex.push(.{ + .position = @bitCast(topLeft), + .uv = @bitCast(uvTopLeft), + .color = quadColor, + }); + + try vertex.push(.{ + .position = @bitCast(topLeft.add(.{ .x = size.x })), + .uv = @bitCast(uvTopLeft.add(.{ .x = uvSize.x })), + .color = quadColor, + }); + + try vertex.push(.{ + .position = @bitCast(topLeft.add(size)), + .uv = @bitCast(uvTopLeft.add(uvSize)), + .color = quadColor, + }); + + try vertex.push(.{ + .position = @bitCast(topLeft.add(.{ .y = size.y })), + .uv = @bitCast(uvTopLeft.add(.{ .y = uvSize.y })), + .color = quadColor, + }); + + try index.push(o + 0); + try index.push(o + 1); + try index.push(o + 2); + + try index.push(o + 2); + try index.push(o + 3); + try index.push(o + 0); +} + +pub const TextRenderer = struct { + // operation of the text renderer. + // given + + textInstances: std.ArrayListUnmanaged(*TextMeshBuffer) = .{}, + deadList: std.ArrayListUnmanaged(*TextMeshBuffer) = .{}, + assignedBuffers: std.AutoHashMapUnmanaged(papyrus.NodeHandle, *TextMeshBuffer) = .{}, + allocator: std.mem.Allocator, + lastUpdateTime: f64 = 0, + + geo: *papyrus.TextRenderGeometry, + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = .{ + .allocator = allocator, + .geo = try papyrus.TextRenderGeometry.create(allocator), + }; + + return self; + } + + pub fn uploadMesh(self: *@This(), copyPass: *gpu.GPUCopyPass, node: papyrus.NodeHandle, text: papyrus.DrawCommand.PrimitiveText) !*TextMeshBuffer { + var checkGC: bool = false; + if (core.getEngineTime() - self.lastUpdateTime > 5) { + checkGC = true; + } + + // assign meshBuffer + const meshBuffer = try self.getOrAllocMeshBuffer(node, text); + + // upload mesh data to mesh buffer + try self.updateMeshBuffer(meshBuffer, copyPass, text); + + // set the fragment sampler + return meshBuffer; + } + + pub fn updateMeshBuffer(self: *@This(), meshBuffer: *TextMeshBuffer, copyPass: *gpu.GPUCopyPass, text: papyrus.DrawCommand.PrimitiveText) !void { + var z = tracy.ZoneN(@src(), "meshBuffer updates"); + defer z.End(); + const string = text.text.getRead(); + if (string.len == 0) + return; + + var indexList = meshBuffer.mapIndexBufferWriter(); + var vertexList = meshBuffer.mapVertexBufferWriter(); + + //const position = core.Vector2f{.x = text.tl.x, .y = text.tl.y}; + const displaySize = text.textSize; + + // 1. get the font Atlas from the textPrimitive's font file reference + const atlas = papyrus.PapyrusRuntime.get().getFont(text.fontHandle) orelse { + meshBuffer.unmapAll(); + return; + }; + + meshBuffer.isSDF = atlas.isSDF; + meshBuffer.fontHandle = text.fontHandle; + + const ratio = displaySize / atlas.fontSize; + const stride = @as(f32, @floatFromInt(atlas.glyphMetrics['l'].x)) * ratio; + + var xOffset: f32 = 0; + var yOffset: f32 = 0; + + const fontHeight = @as(f32, @floatFromInt(atlas.glyphMetrics['l'].y)) * ratio; + + self.geo.setCharHeight(fontHeight); + self.geo.setPosition(text.tl); + try self.geo.addGeoLine(yOffset + text.tl.y, 0); + + var largestXOffset: f32 = 0; + + for (string, 0..) |ch, i| { + // if (i * 4 > self.mesh.maxVertexCount - 16) { + //break; + // } + + if (!atlas.hasGlyph[ch]) { + try self.geo.addCharGeo(text.tl.x + xOffset, stride, @intCast(i)); + xOffset += stride; + continue; + } + + if (ch == 0 or ch == '\r') { + continue; + } + + if (ch == ' ' or (ch == '\n' and text.renderMode == .NoControl)) { + try self.geo.addCharGeo(text.tl.x + xOffset, stride, @intCast(i)); + xOffset += stride; + continue; + } + + // newline if we see newline and we're in simple or rich mode. + if (ch == '\n' and (text.renderMode == .Simple or text.renderMode == .Rich)) { + try self.geo.addCharGeo(text.tl.x + xOffset, stride, @intCast(i)); + xOffset = 0; + yOffset += fontHeight * 1.2; + try self.geo.addGeoLine(yOffset + text.tl.y, @intCast(i)); + continue; + } + + if (ch == ' ') { + try self.geo.addCharGeo(text.tl.x + xOffset, stride, @intCast(i)); + xOffset += stride; + continue; + } + + const box = core.Vector2f.from(atlas.glyphBox1[ch]).fmul(ratio); + const metrics = core.Vector2f.from(atlas.glyphMetrics[ch]).fmul(ratio); + const baseMetrics = core.Vector2f.from(atlas.glyphMetrics[ch]); + + const uv_tl = atlas.glyphCoordinates[ch][0]; + + xOffset += box.x; + + //if (xOffset + box.x + metrics.x > self.boxSize.x) { + if (xOffset + box.x + metrics.x > text.size.x) { + xOffset = 0; + yOffset += fontHeight * 1.2; + try self.geo.addGeoLine(yOffset + text.tl.y, @intCast(i)); + } + + const color = text.color; + + const topLeft = core.Vector2f{ + // .x = self.position.x + xOffset + box.x, + // .y = yOffset + self.position.y + box.y + fontHeight, + .x = xOffset, + .y = yOffset + box.y + fontHeight, + }; + + // core.engine_log("topleft = {d}x{d}", .{ topLeft.x, topLeft.y }); + + const metric_size = core.Vector2f{ .x = metrics.x, .y = metrics.y }; + + try pushQuadToWriters( + topLeft, // topLeft: core.Vector2f, + metric_size, // size: core.Vector2f, + .{ .x = uv_tl.x, .y = uv_tl.y }, // uvTopLeft: core.Vector2f, + .{ + .x = baseMetrics.x / @as(f32, @floatFromInt(atlas.atlasSize.x)), + .y = baseMetrics.y / @as(f32, @floatFromInt(atlas.atlasSize.y)), + }, // uv size + .{ .r = color.r, .g = color.g, .b = color.b }, // color + // uvSize: core.Vector2f, + // color: core.colors.Color, + // vertex: *TextBufferList(TextMeshVertex), + // index: *TextBufferList(u32), + &vertexList, + &indexList, + ); + + // self.mesh.addQuad2D( + // topLeft, + // metric_size, + // .{ .x = uv_tl.x, .y = uv_tl.y }, // uv topleft + // .{ + // .x = baseMetrics.x / @as(f32, @floatFromInt(atlas.atlasSize.x)), + // .y = baseMetrics.y / @as(f32, @floatFromInt(atlas.atlasSize.y)), + // }, // uv size + // .{ .r = color.r, .g = color.g, .b = color.b }, // color + // ); + + // todo insert geo + //try self.renderedGeo.addCharGeo(self.position.x + xOffset, box.x + metrics.x, @intCast(i)); + //xOffset += box.x + metrics.x; + + try self.geo.addCharGeo(text.tl.x + xOffset, metrics.x, @intCast(i)); + xOffset += metrics.x; + + if (xOffset > largestXOffset) { + largestXOffset = xOffset; + } + } + + try self.geo.addCharGeo(text.tl.x + xOffset, 200.0, @intCast(string.len)); + const renderedSize = .{ + .x = largestXOffset, + .y = yOffset + fontHeight * 1.2, + }; + + self.geo.setBoundsX(text.tl.x, text.tl.x + renderedSize.x); + + // pushQuadToWriters( + // // topLeft: core.Vector2f, + // // size: core.Vector2f, + // // uvTopLeft: core.Vector2f, + // // uvSize: core.Vector2f, + // // color: core.colors.Color, + // vertexList, // vertex: *TextBufferList(TextMeshVertex), + // indexList, + // ); //index: *TextBufferList(u32),); + + meshBuffer.unmapAll(); + + meshBuffer.submitBuffers(copyPass); + + meshBuffer.indexCount = indexList.count; + } + + pub fn getOrAllocMeshBuffer(self: *@This(), node: papyrus.NodeHandle, text: papyrus.DrawCommand.PrimitiveText) !*TextMeshBuffer { + const textLen = text.text.getRead().len; + + // 1. get the assigned buffer; + if (self.assignedBuffers.get(node)) |buffer| { + if (textLen <= buffer.capacity) { + return buffer; + } + + core.engine_log("recycling text buffer {any}", .{node}); + // if the buffer is too small for the text, dont use that one and add it to the dead list. + try self.deadList.append(self.allocator, buffer); + _ = self.assignedBuffers.remove(node); + } + + // 2. if no text renderer is available, search through the deadList until you find a TextMeshBuffer + // which has a capacity large enough for the text we're trying to render. + { + var foundBuffer: ?*TextMeshBuffer = null; + var foundIndex: usize = 0; + + for (self.deadList.items, 0..) |buffer, i| { + if (textLen <= buffer.capacity) { + try self.assignedBuffers.put(self.allocator, node, buffer); + foundIndex = i; + foundBuffer = buffer; + } + } + + if (foundBuffer) |buffer| { + _ = self.deadList.swapRemove(foundIndex); + return buffer; + } + } + + // 3. if we don't have any allocations already available in the deadList create a new one large enough for this text + const textLengthBins: []const u32 = &.{ 32, 64, 256, 512, 1024, 4096, 8192, 8192 * 4 }; + + var newBufferLength: u32 = @intFromFloat(@as(f32, @floatFromInt(textLen)) * 1.5); + + for (textLengthBins) |bin| { + if (textLen <= bin) { + newBufferLength = bin; + break; + } + } + + core.engine_log("creating text buffer {any} with length {d} for textLen = {d}", .{ node, newBufferLength, textLen }); + const newBuffer = try TextMeshBuffer.create(self.allocator, newBufferLength); + try self.assignedBuffers.put(self.allocator, node, newBuffer); + try self.textInstances.append(self.allocator, newBuffer); + return newBuffer; + } + + // once every 5 seconds mark the frame as a GC frame + // during a GC frame any TextMeshBuffers which aren't being used get kicked to the deadList + // any TextMeshBuffers which stay in the deadList for 10 or more GC frames get released and removed from the deadlist + + pub fn bindFontBuffersAndSamplers( + self: *@This(), + pass: *gpu.GPURenderPass, + meshBuffer: *TextMeshBuffer, + ) void { + pass.bindGPUVertexBuffers(0, &.{ .buffer = meshBuffer.vertexBuffer, .offset = 0 }, 1); + pass.bindGPUIndexBuffer(&.{ .buffer = meshBuffer.indexBuffer, .offset = 0 }, .indexelementsize32bit); + + // pass.bindGPUFragmentSamplers(0, meshBuffer.fragmentSampler, 1); + + //pass.bindGPUFragmentSamplers(0, &.{ + //.sampler = rend.context().blockySampler, + //.texture = rend.context().defaultTexture.texture, + //}, 1); + + _ = self; + } + + pub fn destroy(self: *@This()) void { + for (self.textInstances.items) |item| { + item.destroy(self.allocator); + } + + self.geo.destroy(); + self.assignedBuffers.deinit(self.allocator); + self.textInstances.deinit(self.allocator); + self.allocator.destroy(self); + } +}; + +pub const TextMeshBuffer = struct { + indexBuffer: *gpu.GPUBuffer = undefined, + vertexBuffer: *gpu.GPUBuffer = undefined, + indexTransferBuffer: *gpu.GPUTransferBuffer = undefined, + vertexTransferBuffer: *gpu.GPUTransferBuffer = undefined, + indexCount: u32 = 0, + isSDF: bool = true, + fontHandle: u32 = 0, + capacity: u32 = 0, + + pub fn create(allocator: std.mem.Allocator, maxChars: u32) !*@This() { + var self: *@This() = try allocator.create(@This()); + self.capacity = maxChars; + const vertexBufferCount: u32 = maxChars * 4; + const indexBufferCount: u32 = maxChars * 6; + + const ctx = rend.context(); + + self.vertexTransferBuffer = ctx.device.createGPUTransferBuffer(&.{ + .usage = .transferbufferusageUpload, + .size = vertexBufferCount * @sizeOf(TextMeshVertex), + .props = 0, + }); + + self.indexTransferBuffer = ctx.device.createGPUTransferBuffer(&.{ + .usage = .transferbufferusageUpload, + .size = indexBufferCount * @sizeOf(u32), + .props = 0, + }); + + self.vertexBuffer = ctx.device.createGPUBuffer(&.{ + .usage = .{ .bufferusageVertex = true }, + .size = vertexBufferCount * @sizeOf(TextMeshVertex), + .props = 0, + }); + + self.indexBuffer = ctx.device.createGPUBuffer(&.{ + .usage = .{ .bufferusageIndex = true }, + .size = indexBufferCount * @sizeOf(u32), + .props = 0, + }); + + return self; + } + + pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void { + const ctx = rend.context(); + + ctx.device.releaseGPUBuffer(self.indexBuffer); + ctx.device.releaseGPUBuffer(self.vertexBuffer); + ctx.device.releaseGPUTransferBuffer(self.indexTransferBuffer); + ctx.device.releaseGPUTransferBuffer(self.vertexTransferBuffer); + + allocator.destroy(self); + } + + // buffer management stuff below + pub fn mapIndexBuffer(self: *@This()) []u32 { + const ctx = rend.context(); + + var slice: []u32 = undefined; + + slice.ptr = @ptrCast(@alignCast(ctx.device.mapGPUTransferBuffer(self.indexTransferBuffer, true))); + slice.len = self.capacity * 6; + + return slice; + } + + pub fn unmapIndexBuffer(self: *@This()) void { + const ctx = rend.context(); + ctx.device.unmapGPUTransferBuffer(self.indexTransferBuffer); + } + + pub fn mapVertexBuffer(self: *@This()) []TextMeshVertex { + const ctx = rend.context(); + + var slice: []TextMeshVertex = undefined; + + slice.ptr = @ptrCast(@alignCast(ctx.device.mapGPUTransferBuffer(self.vertexTransferBuffer, true))); + slice.len = self.capacity * 4; + + return slice; + } + + pub fn unmapVertexBuffer(self: *@This()) void { + const ctx = rend.context(); + ctx.device.unmapGPUTransferBuffer(self.vertexTransferBuffer); + } + + pub fn mapVertexBufferWriter(self: *@This()) TextBufferList(TextMeshVertex) { + const slice = self.mapVertexBuffer(); + return TextBufferList(TextMeshVertex).init(slice); + } + + pub fn mapIndexBufferWriter(self: *@This()) TextBufferList(u32) { + const slice = self.mapIndexBuffer(); + return TextBufferList(u32).init(slice); + } + + pub fn unmapAll(self: *@This()) void { + self.unmapIndexBuffer(); + self.unmapVertexBuffer(); + } + + pub fn submitBuffers(self: *@This(), copyPass: *gpu.GPUCopyPass) void { + copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.indexTransferBuffer, .offset = 0 }, &.{ + .buffer = self.indexBuffer, + .offset = 0, + .size = self.capacity * 6 * @sizeOf(u32), + }, false); + + copyPass.uploadToGPUBuffer(&.{ .transfer_buffer = self.vertexTransferBuffer, .offset = 0 }, &.{ + .buffer = self.vertexBuffer, + .offset = 0, + .size = self.capacity * 4 * @sizeOf(TextMeshVertex), + }, false); + } }; pub const text_frag = @import("text.frag"); pub const text_vert = @import("text.vert"); pub const rend = @import("rend"); +pub const core = @import("core"); +pub const tracy = core.tracy; pub const std = @import("std"); const sdl = @import("sdl3"); const gpu = sdl.gpu; +const papyrus = @import("papyrus"); diff --git a/projects/content/_shaders/dxil/text.frag.dxil b/projects/content/_shaders/dxil/text.frag.dxil index 8cc0ab4..f8d8799 100644 Binary files a/projects/content/_shaders/dxil/text.frag.dxil and b/projects/content/_shaders/dxil/text.frag.dxil differ diff --git a/projects/content/_shaders/dxil/text.vert.dxil b/projects/content/_shaders/dxil/text.vert.dxil index 091037a..33fa0b5 100644 Binary files a/projects/content/_shaders/dxil/text.vert.dxil and b/projects/content/_shaders/dxil/text.vert.dxil differ diff --git a/projects/content/_shaders/msl/text.frag.msl b/projects/content/_shaders/msl/text.frag.msl index 6bae432..b3e2448 100644 --- a/projects/content/_shaders/msl/text.frag.msl +++ b/projects/content/_shaders/msl/text.frag.msl @@ -6,11 +6,10 @@ using namespace metal; struct FontInfo { float2 position; - float size; + float2 size; uint isSdf; uint pad0; - packed_float2 pad2; - char _m0_final_padding[4]; + float2 pad2; }; struct type_StructuredBuffer_FontInfo @@ -18,7 +17,7 @@ struct type_StructuredBuffer_FontInfo FontInfo _m0[1]; }; -constant float _60 = {}; +constant float _43 = {}; struct main0_out { @@ -29,77 +28,26 @@ struct main0_in { uint in_var_TEXCOORD0 [[user(locn0)]]; float2 in_var_TEXCOORD1 [[user(locn1)]]; - float2 in_var_TEXCOORD2 [[user(locn2)]]; uint in_var_TEXCOORD3 [[user(locn3)]]; }; fragment main0_out main0(main0_in in [[stage_in]], const device type_StructuredBuffer_FontInfo& fontBuffer [[buffer(0)]], texture2d Texture0 [[texture(0)]], sampler Sampler0 [[sampler(0)]]) { main0_out out = {}; - float4 _68 = Texture0.sample(Sampler0, in.in_var_TEXCOORD1); - bool _103; - do + float4 _51 = Texture0.sample(Sampler0, in.in_var_TEXCOORD1); + float4 _65 = float4(float(in.in_var_TEXCOORD0 & 255u) * 0.0039215688593685626983642578125, float((in.in_var_TEXCOORD0 >> 8u) & 255u) * 0.0039215688593685626983642578125, float((in.in_var_TEXCOORD0 >> 16u) & 255u) * 0.0039215688593685626983642578125, _43); + float4 _73; + if (fontBuffer._m0[in.in_var_TEXCOORD3].isSdf == 0u) { - bool _85; - if (in.in_var_TEXCOORD2.x >= fontBuffer._m0[in.in_var_TEXCOORD3].position.x) - { - _85 = in.in_var_TEXCOORD2.x <= (fontBuffer._m0[in.in_var_TEXCOORD3].position.x + fontBuffer._m0[in.in_var_TEXCOORD3].size); - } - else - { - _85 = false; - } - bool _92; - if (_85) - { - _92 = in.in_var_TEXCOORD2.y >= fontBuffer._m0[in.in_var_TEXCOORD3].position.y; - } - else - { - _92 = false; - } - bool _100; - if (_92) - { - _100 = in.in_var_TEXCOORD2.y <= (fontBuffer._m0[in.in_var_TEXCOORD3].position.y + fontBuffer._m0[in.in_var_TEXCOORD3].size); - } - else - { - _100 = false; - } - if (_100) - { - _103 = true; - break; - } - _103 = false; - break; - } while(false); - if (!_103) - { - discard_fragment(); - } - float _109 = float(in.in_var_TEXCOORD0 & 255u) * 0.0039215688593685626983642578125; - float _113 = float((in.in_var_TEXCOORD0 >> 8u) & 255u) * 0.0039215688593685626983642578125; - float _117 = float((in.in_var_TEXCOORD0 >> 16u) & 255u) * 0.0039215688593685626983642578125; - float4 _180; - if (fontBuffer._m0[in.in_var_TEXCOORD3].isSdf == 1u) - { - float _123 = _68.x; - float _124 = fwidth(_123); - float _125 = 0.529411792755126953125 - _124; - float _126 = 0.529411792755126953125 + _124; - float2 _132 = (dfdx(in.in_var_TEXCOORD1) + dfdy(in.in_var_TEXCOORD1)) * 0.3540000021457672119140625; - float4 _139 = float4(in.in_var_TEXCOORD1 - _132, in.in_var_TEXCOORD1 + _132); - float4 _170 = float4(_109, _113, _117, (fast::clamp(smoothstep(_125, _126, _123), 0.0, 1.0) + (0.5 * (((fast::clamp(smoothstep(_125, _126, Texture0.sample(Sampler0, _139.xy).x), 0.0, 1.0) + fast::clamp(smoothstep(_125, _126, Texture0.sample(Sampler0, _139.zw).x), 0.0, 1.0)) + fast::clamp(smoothstep(_125, _126, Texture0.sample(Sampler0, _139.xw).x), 0.0, 1.0)) + fast::clamp(smoothstep(_125, _126, Texture0.sample(Sampler0, _139.zy).x), 0.0, 1.0)))) * 0.3333333432674407958984375); - float3 _172 = powr(_170.xyz, float3(2.2000000476837158203125)); - _180 = float4(_172.x, _172.y, _172.z, _170.w); + float3 _71 = powr(_65.xyz, float3(2.2000000476837158203125)); + _73 = float4(_71.x, _71.y, _71.z, _65.w); } else { - _180 = float4(_109, _113, _117, powr(_68.x / dot(float4(_109, _113, _117, _60).xyz, float3(0.2125999927520751953125, 0.715200006961822509765625, 0.072200000286102294921875)), 0.4545454680919647216796875)); + _73 = _65; } - out.out_var_SV_Target0 = _180; + float _74 = _51.x; + out.out_var_SV_Target0 = float4(1.0, 1.0, 1.0, _74) + (float4(_73.xyz, _74) * 9.9999997473787516355514526367188e-06); return out; } diff --git a/projects/content/_shaders/msl/text.vert.msl b/projects/content/_shaders/msl/text.vert.msl index 2021ed7..8f4fe39 100644 --- a/projects/content/_shaders/msl/text.vert.msl +++ b/projects/content/_shaders/msl/text.vert.msl @@ -6,11 +6,10 @@ using namespace metal; struct FontInfo { float2 position; - float size; + float2 size; uint isSdf; uint pad0; - packed_float2 pad2; - char _m0_final_padding[4]; + float2 pad2; }; struct type_StructuredBuffer_FontInfo @@ -43,11 +42,14 @@ vertex main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniform { main0_out out = {}; float2 _46 = in.in_var_TEXCOORD0 + fontBuffer._m0[gl_InstanceIndex].position; + float2 _51 = ((_46 / Uniforms.Extents) * 2.0) + float2(-1.0); + float4 _54 = float4(_51, 0.0, 1.0); + _54.y = -_51.y; out.out_var_TEXCOORD0 = in.in_var_TEXCOORD2; out.out_var_TEXCOORD1 = in.in_var_TEXCOORD1; out.out_var_TEXCOORD2 = _46; out.out_var_TEXCOORD3 = gl_InstanceIndex; - out.gl_Position = float4(((_46 / Uniforms.Extents) * 2.0) + float2(-1.0), 0.0, 1.0); + out.gl_Position = _54; return out; } diff --git a/projects/content/_shaders/spv/text.frag.spv b/projects/content/_shaders/spv/text.frag.spv index 15ad3b5..240a198 100644 Binary files a/projects/content/_shaders/spv/text.frag.spv and b/projects/content/_shaders/spv/text.frag.spv differ diff --git a/projects/content/_shaders/spv/text.vert.spv b/projects/content/_shaders/spv/text.vert.spv index 33029eb..c2daae7 100644 Binary files a/projects/content/_shaders/spv/text.vert.spv and b/projects/content/_shaders/spv/text.vert.spv differ diff --git a/projects/sampleGame/externGame/externGame.zig b/projects/sampleGame/externGame/externGame.zig index f95398f..8c1ddb7 100644 --- a/projects/sampleGame/externGame/externGame.zig +++ b/projects/sampleGame/externGame/externGame.zig @@ -97,7 +97,7 @@ pub const ExternGameObject = struct { ctx.getPanel(panel2).hasTitle = true; //ctx.get(panel2).anchor = .TopRight; // ctx.get(panel2).fill = .FillY; todo, this is a bug. why. - ctx.get(panel2).pos = .{ .x = 200, .y = 200 }; + ctx.get(panel2).pos = .{ .x = 900, .y = 200 }; ctx.get(panel2).size = .{ .x = 100, .y = 100 }; ctx.get(panel2).style.backgroundColor = ui.ModernStyle.GreyDark; ctx.getPanel(panel2).titleColor = ui.ModernStyle.GreyDark; diff --git a/projects/tools/toolbox.zig b/projects/tools/toolbox.zig index a581fd9..d52ea6c 100644 --- a/projects/tools/toolbox.zig +++ b/projects/tools/toolbox.zig @@ -36,20 +36,22 @@ pub fn prepare(self: *@This()) !void { pub fn tick(self: *@This(), dt: f64) void { _ = dt; + std.time.sleep(10 * 1000 * 1000); + self.tickTasks() catch {}; if (self.activeCommand != null or self.commandQueue.count() > 0) { - ig.setNextWindowPos(.{ .x = 20, .y = 20 }, .{}, .{}); + //ig.setNextWindowPos(.{ .x = 20, .y = 20 }, .{}, .{}); - if (ig.begin("tasks", null, .{ - .always_auto_resize = true, - .no_title_bar = true, - .no_move = true, - .no_resize = true, - .no_collapse = true, - })) {} + // if (ig.begin("tasks", null, .{ + // .always_auto_resize = true, + // .no_title_bar = true, + // .no_move = true, + // .no_resize = true, + // .no_collapse = true, + // })) {} - ig.end(); + // ig.end(); } // Initialize docking layout on first run @@ -68,8 +70,12 @@ pub fn tick(self: *@This(), dt: f64) void { if (ig.begin("Toolbox Window", null, .{})) { ig.textf("This is a docked toolbox window", .{}); - if (ig.button("Recompile Shaders", .{})) { - start_RecompileShaders() catch {}; + if (ig.button("Compile Shaders", .{})) { + start_CompileShaders() catch {}; + } + + if (ig.button("Launch TrenchBroom", .{})) { + start_TrenchBroom() catch {}; } ig.separator(); @@ -91,7 +97,118 @@ pub fn tick(self: *@This(), dt: f64) void { ig.end(); } -fn start_RecompileShaders() !void {} +fn findRootDir(allocator: std.mem.Allocator) ![]u8 { + var current_dir = std.fs.cwd(); + + // Start from current directory and walk up + var path_components = std.ArrayList([]const u8).init(allocator); + defer path_components.deinit(); + + // Try current directory first + current_dir.access("content.txt", .{}) catch { + // content.txt not found, need to walk up + var temp_dir = current_dir; + + while (true) { + // Try to go up one directory + const parent_dir = temp_dir.openDir("..", .{}) catch return error.RootNotFound; + temp_dir = parent_dir; + + // Check if content.txt exists in parent directory + temp_dir.access("content.txt", .{}) catch { + continue; + }; + + // Found content.txt, get the absolute path + return try temp_dir.realpathAlloc(allocator, "."); + } + }; + + // content.txt found in current directory + return try current_dir.realpathAlloc(allocator, "."); +} + +fn start_CompileShaders() !void { + const self = core.getEngineObject(@This()) orelse return; + + const root_dir = findRootDir(self.allocator) catch { + core.engine_log("Could not find BacklogEngine root directory (content.txt not found)", .{}); + return; + }; + defer self.allocator.free(root_dir); + core.engine_log("Found root directory: {s}", .{root_dir}); + + const compile_shaders_dir = try std.fs.path.join(self.allocator, &.{ root_dir, "tools", "compileShaders" }); + defer self.allocator.free(compile_shaders_dir); + + var compile_shaders_name = core.MakeName(compile_shaders_dir); + + core.engine_log("CompileShaders directory: {s}", .{compile_shaders_dir}); + + const exe_path = try std.fs.path.join(self.allocator, &.{ compile_shaders_dir, "zig-out", "bin", "compileShaders.exe" }); + defer self.allocator.free(exe_path); + core.engine_log("Looking for executable at: {s}", .{exe_path}); + + // Check if compileShaders.exe exists + if (std.fs.cwd().access(exe_path, .{})) |_| { + // File exists, run it directly + core.engine_log("CompileShaders executable found, running directly", .{}); + try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand( + self.allocator, + &.{"zig-out/bin/compileShaders.exe"}, + compile_shaders_name.utf8(), + ) }); + } else |_| { + // File doesn't exist, build it first + core.engine_log("CompileShaders executable not found, building first", .{}); + try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand( + self.allocator, + &.{ "zig", "build", "install" }, + compile_shaders_name.utf8(), + ) }); + + // Then run it + core.engine_log("Queuing compileShaders execution after build", .{}); + try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand( + self.allocator, + &.{"zig-out/bin/compileShaders.exe"}, + compile_shaders_name.utf8(), + ) }); + } +} + +pub fn launchDetachedProcess(allocator: std.mem.Allocator, argv: []const []const u8, cwd: ?[]const u8) !void { + var child = std.process.Child.init(argv, allocator); + child.cwd = cwd; + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Ignore; + + try child.spawn(); +} + +fn start_TrenchBroom() !void { + const self = core.getEngineObject(@This()) orelse return; + + const root_dir = findRootDir(self.allocator) catch { + core.engine_log("Could not find BacklogEngine root directory (content.txt not found)", .{}); + return; + }; + defer self.allocator.free(root_dir); + + const trenchbroom_dir = try std.fs.path.join(self.allocator, &.{ root_dir, "tools", "trenchbroom" }); + defer self.allocator.free(trenchbroom_dir); + + var trenchbroom_name = core.MakeName(trenchbroom_dir); + + core.engine_log("Launching TrenchBroom from: {s}", .{trenchbroom_dir}); + + try launchDetachedProcess( + self.allocator, + &.{"TrenchBroom.exe"}, + trenchbroom_name.utf8(), + ); +} fn tickTasks(self: *@This()) !void { if (self.activeCommand == null) {