diff --git a/README.md b/README.md index 4e99623..24e247c 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ vkCmdCopyImageToBuffer | ✅ Implemented vkCmdCopyQueryPoolResults | ⚙️ WIP vkCmdDispatch | ✅ Implemented vkCmdDispatchIndirect | ✅ Implemented -vkCmdDraw | ⚙️ WIP +vkCmdDraw | ✅ Implemented vkCmdDrawIndexed | ⚙️ WIP vkCmdDrawIndexedIndirect | ⚙️ WIP vkCmdDrawIndirect | ⚙️ WIP diff --git a/build.zig b/build.zig index fc3de6a..7d455ae 100644 --- a/build.zig +++ b/build.zig @@ -6,7 +6,7 @@ const ImplementationDesc = struct { name: []const u8, root_source_file: []const u8, vulkan_version: std.SemanticVersion, - custom: ?*const fn (*std.Build, *std.Build.Step.Compile, *std.Build.Step.Options) anyerror!void = null, + custom: ?*const fn (*std.Build, *std.Build.Step.Compile, *std.Build.Step.Options, bool) anyerror!void = null, }; const implementations = [_]ImplementationDesc{ @@ -76,7 +76,7 @@ pub fn build(b: *std.Build) !void { }); if (impl.custom) |custom| { - custom(b, lib, options) catch continue; + custom(b, lib, options, use_llvm) catch continue; } const icd_file = b.addWriteFile( @@ -143,7 +143,7 @@ pub fn build(b: *std.Build) !void { docs_step.dependOn(&install_docs.step); } -fn customSoft(b: *std.Build, lib: *std.Build.Step.Compile, options: *std.Build.Step.Options) !void { +fn customSoft(b: *std.Build, lib: *std.Build.Step.Compile, options: *std.Build.Step.Options, use_llvm: bool) !void { const cpuinfo = b.lazyDependency("cpuinfo", .{}) orelse return error.UnresolvedDependency; lib.root_module.addSystemIncludePath(cpuinfo.path("include")); lib.root_module.linkLibrary(cpuinfo.artifact("cpuinfo")); @@ -151,7 +151,7 @@ fn customSoft(b: *std.Build, lib: *std.Build.Step.Compile, options: *std.Build.S const spv = b.lazyDependency("SPIRV_Interpreter", .{ .@"no-example" = true, .@"no-test" = true, - .@"use-llvm" = true, + .@"use-llvm" = use_llvm, }) orelse return error.UnresolvedDependency; lib.root_module.addImport("spv", spv.module("spv")); diff --git a/build.zig.zon b/build.zig.zon index 8179baa..44a5f0f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -31,10 +31,14 @@ .hash = "zmath-0.11.0-dev-wjwivdMsAwD-xaLj76YHUq3t9JDH-X16xuMTmnDzqbu2", }, .SPIRV_Interpreter = .{ - .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#5b7380eea014dce11587597d44681640a3a3583b", - .hash = "SPIRV_Interpreter-0.0.1-ajmpnwaZBAA5PJz7wTcWkWPywJAJF672vYjuCZKO4t9j", + .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#9cdb683f3f24cf82774f1bf31b8c51d3863a9cfe", + .hash = "SPIRV_Interpreter-0.0.1-ajmpn1SyBAC5RYKryNkL2llgGFKcLPFGhCmxcraJ2eSL", .lazy = true, }, + //.SPIRV_Interpreter = .{ + // .path = "../SPIRV-Interpreter", + // .lazy = true, + //}, }, .paths = .{ diff --git a/src/soft/SoftCommandBuffer.zig b/src/soft/SoftCommandBuffer.zig index f384b89..343ec5b 100644 --- a/src/soft/SoftCommandBuffer.zig +++ b/src/soft/SoftCommandBuffer.zig @@ -66,6 +66,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .reset = reset, .resetEvent = resetEvent, .setEvent = setEvent, + .setViewport = setViewport, .waitEvents = waitEvents, }; @@ -621,6 +622,31 @@ pub fn setEvent(interface: *Interface, event: *base.Event, stage: vk.PipelineSta _ = stage; } +pub fn setViewport(interface: *Interface, first: u32, viewports: []const vk.Viewport) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + first: u32, + viewports: []const vk.Viewport, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + device.renderer.dynamic_state.viewports = impl.viewports; // Unsafe + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ + .first = first, + .viewports = allocator.dupe(vk.Viewport, viewports) catch return VkError.OutOfHostMemory, // Will be freed on cmdbuf reset or destroy + }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + pub fn waitEvents(interface: *Interface, events: []*const base.Event, src_stage: vk.PipelineStageFlags, dst_stage: vk.PipelineStageFlags, memory_barriers: []const vk.MemoryBarrier, buffer_barriers: []const vk.BufferMemoryBarrier, image_barriers: []const vk.ImageMemoryBarrier) VkError!void { // No-op _ = interface; diff --git a/src/soft/device/Renderer.zig b/src/soft/device/Renderer.zig index 19462d3..2694773 100644 --- a/src/soft/device/Renderer.zig +++ b/src/soft/device/Renderer.zig @@ -3,6 +3,7 @@ const vk = @import("vulkan"); const base = @import("base"); const zm = base.zm; const lib = @import("../lib.zig"); +const spv = @import("spv"); pub const F32x4 = zm.F32x4; @@ -32,18 +33,24 @@ pub const VertexBuffer = struct { }; pub const DynamicState = struct { - viewport: vk.Viewport, - scissor: vk.Rect2D, - line_width: f32, + viewports: ?[]const vk.Viewport, + scissor: ?[]vk.Rect2D, + line_width: ?f32, +}; + +pub const Vertex = struct { + position: F32x4, + outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]?[]u8, }; pub const Fragment = struct { position: F32x4, color: F32x4, + inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8, }; pub const DrawCall = struct { - vertices: []F32x4, + vertices: []Vertex, fragments: []Fragment, }; @@ -60,11 +67,17 @@ pub fn init(device: *SoftDevice, state: *PipelineState) Self { .state = state, .render_pass = null, .framebuffer = null, - .dynamic_state = undefined, + .dynamic_state = .{ + .viewports = null, + .scissor = null, + .line_width = null, + }, }; } pub fn draw(self: *Self, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) VkError!void { + const io = self.device.interface.io(); + const render_target_view: *base.ImageView = (self.framebuffer orelse return).interface.attachments[0]; const render_target: *SoftImage = @alignCast(@fieldParentPtr("interface", render_target_view.image)); const render_target_memory = if (render_target.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv; @@ -73,20 +86,32 @@ pub fn draw(self: *Self, vertex_count: usize, instance_count: usize, first_verte defer arena.deinit(); const allocator = arena.allocator(); + const timer = std.Io.Timestamp.now(io, .real); + defer if (comptime base.config.logs) { + const duration = timer.untilNow(io, .real); + const ms = duration.toMicroseconds(); + std.log.scoped(.SoftwareRenderer).debug("Drawcall stats:\n> Took {d}us\n> Allocated {d} KB", .{ ms, @divTrunc(arena.queryCapacity(), 1000) }); + }; + var draw_call: DrawCall = .{ - .vertices = allocator.alloc(F32x4, vertex_count * instance_count) catch return VkError.OutOfDeviceMemory, + .vertices = allocator.alloc(Vertex, vertex_count * instance_count) catch return VkError.OutOfDeviceMemory, .fragments = undefined, }; - self.vertexShaderStage(&draw_call, vertex_count, instance_count) catch |err| { + for (draw_call.vertices) |*vertex| { + vertex.outputs = [_]?[]u8{null} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS; + } + + self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count) catch |err| { std.log.scoped(.@"Vertex stage").err("catched a '{s}'", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpErrorReturnTrace(trace); } }; - self.primitiveAssemblyStage(&draw_call); + try self.primitiveAssemblyStage(&draw_call); try self.rasterizationStage(allocator, &draw_call); + self.fragmentShaderStage(&draw_call) catch |err| { std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { @@ -121,7 +146,7 @@ pub fn deinit(self: *Self) void { _ = self; } -fn vertexShaderStage(self: *Self, draw_call: *DrawCall, vertex_count: usize, instance_count: usize) !void { +fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *DrawCall, vertex_count: usize, instance_count: usize) !void { const pipeline = self.state.pipeline orelse return; const batch_size = (pipeline.stages.getPtr(.vertex) orelse return).runtimes.len; @@ -129,6 +154,7 @@ fn vertexShaderStage(self: *Self, draw_call: *DrawCall, vertex_count: usize, ins for (0..instance_count) |instance_index| { for (0..@min(batch_size, vertex_count)) |batch_id| { const run_data: vertex_dispatcher.RunData = .{ + .allocator = allocator, .renderer = self, .pipeline = pipeline, .batch_id = batch_id, @@ -144,14 +170,23 @@ fn vertexShaderStage(self: *Self, draw_call: *DrawCall, vertex_count: usize, ins wg.await(self.device.interface.io()) catch return VkError.DeviceLost; } -fn primitiveAssemblyStage(self: *Self, draw_call: *DrawCall) void { - const viewport = (self.state.pipeline orelse return).interface.mode.graphics.viewport_state.viewports[0]; +fn primitiveAssemblyStage(self: *Self, draw_call: *DrawCall) VkError!void { + const viewport = blk: { + const pipeline_data = &(self.state.pipeline orelse return VkError.InvalidPipelineDrv).interface.mode.graphics; + if (pipeline_data.dynamic_state.viewport) { + if (self.dynamic_state.viewports) |viewports| + break :blk viewports[0]; + } + if (pipeline_data.viewport_state.viewports) |viewports| + break :blk viewports[0]; + return VkError.Unknown; + }; for (draw_call.vertices) |*vertex| { - const x = vertex[0]; - const y = vertex[1]; - const z = vertex[2]; - const w = vertex[3]; + const x = vertex.position[0]; + const y = vertex.position[1]; + const z = vertex.position[2]; + const w = vertex.position[3]; // Perspective division. const x_ndc = x / w; @@ -170,7 +205,7 @@ fn primitiveAssemblyStage(self: *Self, draw_call: *DrawCall) void { const y_screen = ((p_y / 2.0) * y_ndc) + o_y; const z_screen = (p_z * z_ndc) + o_z; - vertex.* = zm.f32x4(x_screen, y_screen, z_screen, 1.0); + vertex.position = zm.f32x4(x_screen, y_screen, z_screen, 1.0); } } @@ -182,9 +217,9 @@ fn rasterizationStage(self: *Self, allocator: std.mem.Allocator, draw_call: *Dra switch (topology) { .triangle_list => for (0..@divExact(draw_call.vertices.len, 3)) |triangle_index| { const first_vertex = triangle_index * 3; - const v0 = draw_call.vertices[first_vertex + 0]; - const v1 = draw_call.vertices[first_vertex + 1]; - const v2 = draw_call.vertices[first_vertex + 2]; + const v0 = &draw_call.vertices[first_vertex + 0]; + const v1 = &draw_call.vertices[first_vertex + 1]; + const v2 = &draw_call.vertices[first_vertex + 2]; switch (pipeline_data.rasterization.polygon_mode) { .fill => try rasterizer.drawTriangleFilled(allocator, &fragments, v0, v1, v2), diff --git a/src/soft/device/fragment_dispatcher.zig b/src/soft/device/fragment_dispatcher.zig index 1fcbe96..7826db8 100644 --- a/src/soft/device/fragment_dispatcher.zig +++ b/src/soft/device/fragment_dispatcher.zig @@ -41,6 +41,18 @@ inline fn run(data: RunData) !void { var invocation_index: usize = data.batch_id; while (invocation_index < data.fragment_count) : (invocation_index += data.batch_size) { + const fragment: *Renderer.Fragment = &data.draw_call.fragments[invocation_index]; + + for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { + const result_word = rt.getResultByLocation(@intCast(location), .input) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, + }; + if (result_word != 0) { + try rt.writeInput(fragment.inputs[location], result_word); + } + } + rt.callEntryPoint(allocator, entry) catch |err| switch (err) { // Some errors can be safely ignored SpvRuntimeError.OutOfBounds, @@ -49,8 +61,7 @@ inline fn run(data: RunData) !void { else => return err, }; - const output: *F32x4 = &data.draw_call.fragments[invocation_index].color; - try rt.readOutput(std.mem.asBytes(output), output_result); - output.* = std.math.clamp(output.*, zm.f32x4s(0.0), zm.f32x4s(1.0)); + try rt.readOutput(std.mem.asBytes(&fragment.color), output_result); + fragment.color = std.math.clamp(fragment.color, zm.f32x4s(0.0), zm.f32x4s(1.0)); } } diff --git a/src/soft/device/rasterizer.zig b/src/soft/device/rasterizer.zig index 0ec7740..5b51fa0 100644 --- a/src/soft/device/rasterizer.zig +++ b/src/soft/device/rasterizer.zig @@ -8,14 +8,76 @@ const VkError = base.VkError; const lib = @import("../lib.zig"); const Renderer = @import("Renderer.zig"); +const spv = @import("spv"); pub const F32x4 = zm.F32x4; -pub fn drawLineBresenham(allocator: std.mem.Allocator, fragments: *std.ArrayList(Renderer.Fragment), v0: F32x4, v1: F32x4) VkError!void { - var x0: i32 = @intFromFloat(v0[0]); - var y0: i32 = @intFromFloat(v0[1]); - var x1: i32 = @intFromFloat(v1[0]); - var y1: i32 = @intFromFloat(v1[1]); +fn writePacked(comptime T: type, bytes: []u8, value: T) void { + const raw: [@sizeOf(T)]u8 = @bitCast(value); + @memcpy(bytes[0..@sizeOf(T)], raw[0..]); +} + +fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, b1: f32, b2: f32) F32x4 { + return (value0 * @as(F32x4, @splat(b0))) + (value1 * @as(F32x4, @splat(b1))) + (value2 * @as(F32x4, @splat(b2))); +} + +fn interpolateVertexOutputs( + allocator: std.mem.Allocator, + v0: *const Renderer.Vertex, + v1: *const Renderer.Vertex, + v2: *const Renderer.Vertex, + b0: f32, + b1: f32, + b2: f32, +) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 { + var inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 = undefined; + + for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { + const out0 = v0.outputs[location] orelse continue; + const out1 = v1.outputs[location] orelse continue; + const out2 = v2.outputs[location] orelse continue; + + if (out0.len == 0) { + inputs[location] = out0; + continue; + } + + const len = @min(out0.len, out1.len, out2.len); + const input = allocator.alloc(u8, len) catch return VkError.OutOfDeviceMemory; + + var byte_index: usize = 0; + while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) { + const value0 = std.mem.bytesToValue(F32x4, out0[byte_index..]); + const value1 = std.mem.bytesToValue(F32x4, out1[byte_index..]); + const value2 = std.mem.bytesToValue(F32x4, out2[byte_index..]); + writePacked(F32x4, input[byte_index..], interpolateF32x4(value0, value1, value2, b0, b1, b2)); + } + + while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) { + const value0 = std.mem.bytesToValue(f32, out0[byte_index..]); + const value1 = std.mem.bytesToValue(f32, out1[byte_index..]); + const value2 = std.mem.bytesToValue(f32, out2[byte_index..]); + writePacked(f32, input[byte_index..], (value0 * b0) + (value1 * b1) + (value2 * b2)); + } + + if (byte_index < len) + @memcpy(input[byte_index..], out0[byte_index..len]); + + inputs[location] = input; + } + + return inputs; +} + +fn interpolateLineOutputs(allocator: std.mem.Allocator, v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, t: f32) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 { + return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0); +} + +pub fn drawLineBresenham(allocator: std.mem.Allocator, fragments: *std.ArrayList(Renderer.Fragment), v0: *Renderer.Vertex, v1: *Renderer.Vertex) VkError!void { + var x0: i32 = @intFromFloat(v0.position[0]); + var y0: i32 = @intFromFloat(v0.position[1]); + var x1: i32 = @intFromFloat(v1.position[0]); + var y1: i32 = @intFromFloat(v1.position[1]); const steep = blk: { if (@abs(y1 - y0) > @abs(x1 - x0)) { @@ -26,9 +88,12 @@ pub fn drawLineBresenham(allocator: std.mem.Allocator, fragments: *std.ArrayList break :blk false; }; + var start_vertex = v0; + var end_vertex = v1; if (x0 > x1) { std.mem.swap(i32, &x0, &x1); std.mem.swap(i32, &y0, &y1); + std.mem.swap(*Renderer.Vertex, &start_vertex, &end_vertex); } const d_err = @abs(y1 - y0); @@ -42,10 +107,14 @@ pub fn drawLineBresenham(allocator: std.mem.Allocator, fragments: *std.ArrayList while (x <= x1) : (x += 1) { const x_fragment: f32 = @floatFromInt(if (steep) y else x); const y_fragment: f32 = @floatFromInt(if (steep) x else y); + const t = @as(f32, @floatFromInt(x - x0)) / @as(f32, @floatFromInt(@max(d_x, 1))); + + const z = ((1.0 - t) * start_vertex.position[2]) + (t * end_vertex.position[2]); fragments.append(allocator, .{ - .position = zm.f32x4(x_fragment, y_fragment, 0.0, 1.0), + .position = zm.f32x4(x_fragment, y_fragment, z, 1.0), .color = zm.f32x4(1.0, 1.0, 1.0, 1.0), + .inputs = try interpolateLineOutputs(allocator, start_vertex, end_vertex, t), }) catch return VkError.OutOfDeviceMemory; err -= @intCast(d_err); @@ -60,14 +129,15 @@ fn edgeFunction(a: F32x4, b: F32x4, p: F32x4) f32 { return ((p[0] - a[0]) * (b[1] - a[1])) - ((p[1] - a[1]) * (b[0] - a[0])); } -pub fn drawTriangleFilled(allocator: std.mem.Allocator, fragments: *std.ArrayList(Renderer.Fragment), v0: F32x4, v1: F32x4, v2: F32x4) VkError!void { - const min_x: i32 = @intFromFloat(@floor(@min(v0[0], @min(v1[0], v2[0])))); - const max_x: i32 = @intFromFloat(@ceil(@max(v0[0], @max(v1[0], v2[0])))); - const min_y: i32 = @intFromFloat(@floor(@min(v0[1], @min(v1[1], v2[1])))); - const max_y: i32 = @intFromFloat(@ceil(@max(v0[1], @max(v1[1], v2[1])))); +pub fn drawTriangleFilled(allocator: std.mem.Allocator, fragments: *std.ArrayList(Renderer.Fragment), v0: *Renderer.Vertex, v1: *Renderer.Vertex, v2: *Renderer.Vertex) VkError!void { + const min_x: i32 = @intFromFloat(@floor(@min(v0.position[0], v1.position[0], v2.position[0]))); + const max_x: i32 = @intFromFloat(@ceil(@max(v0.position[0], v1.position[0], v2.position[0]))); + const min_y: i32 = @intFromFloat(@floor(@min(v0.position[1], v1.position[1], v2.position[1]))); + const max_y: i32 = @intFromFloat(@ceil(@max(v0.position[1], v1.position[1], v2.position[1]))); - const area = edgeFunction(v0, v1, v2); - if (area == 0.0) return; + const area = edgeFunction(v0.position, v1.position, v2.position); + if (area == 0.0) + return; var y = min_y; while (y <= max_y) : (y += 1) { @@ -75,25 +145,27 @@ pub fn drawTriangleFilled(allocator: std.mem.Allocator, fragments: *std.ArrayLis while (x <= max_x) : (x += 1) { const p = zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 0.5, 0.0, 1.0); - const w0 = edgeFunction(v1, v2, p); - const w1 = edgeFunction(v2, v0, p); - const w2 = edgeFunction(v0, v1, p); + const w0 = edgeFunction(v1.position, v2.position, p); + const w1 = edgeFunction(v2.position, v0.position, p); + const w2 = edgeFunction(v0.position, v1.position, p); const inside = if (area > 0.0) w0 >= 0.0 and w1 >= 0.0 and w2 >= 0.0 else w0 <= 0.0 and w1 <= 0.0 and w2 <= 0.0; - if (!inside) continue; + if (!inside) + continue; const b0 = w0 / area; const b1 = w1 / area; const b2 = w2 / area; - const z = (b0 * v0[2]) + (b1 * v1[2]) + (b2 * v2[2]); + const z = (b0 * v0.position[2]) + (b1 * v1.position[2]) + (b2 * v2.position[2]); fragments.append(allocator, .{ .position = zm.f32x4(@floatFromInt(x), @floatFromInt(y), z, 1.0), .color = zm.f32x4(1.0, 1.0, 1.0, 1.0), + .inputs = try interpolateVertexOutputs(allocator, v0, v1, v2, b0, b1, b2), }) catch return VkError.OutOfDeviceMemory; } } diff --git a/src/soft/device/vertex_dispatcher.zig b/src/soft/device/vertex_dispatcher.zig index 05ecd10..074ddd0 100644 --- a/src/soft/device/vertex_dispatcher.zig +++ b/src/soft/device/vertex_dispatcher.zig @@ -12,6 +12,7 @@ const SoftPipeline = @import("../SoftPipeline.zig"); const VkError = base.VkError; pub const RunData = struct { + allocator: std.mem.Allocator, renderer: *Renderer, pipeline: *SoftPipeline, batch_id: usize, @@ -45,20 +46,22 @@ inline fn run(data: RunData) !void { else => return err, }; - for (data.pipeline.interface.mode.graphics.input_assembly.attribute_description orelse return) |attribute| { - const location_result = try rt.getResultByLocation(attribute.location, .input); + if (data.pipeline.interface.mode.graphics.input_assembly.attribute_description) |attributes| { + for (attributes) |attribute| { + const location_result = try rt.getResultByLocation(attribute.location, .input); - const binding_info = (data.pipeline.interface.mode.graphics.input_assembly.binding_description orelse return)[attribute.binding]; + const binding_info = (data.pipeline.interface.mode.graphics.input_assembly.binding_description orelse return)[attribute.binding]; - const vertex_buffer = data.renderer.state.data.graphics.vertex_buffers[attribute.binding]; - const buffer = vertex_buffer.buffer; - const buffer_memory_size = base.format.texelSize(attribute.format); - const buffer_memory = if (buffer.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv; - const offset = buffer.interface.offset + (binding_info.stride * invocation_index) + attribute.offset; + const vertex_buffer = data.renderer.state.data.graphics.vertex_buffers[attribute.binding]; + const buffer = vertex_buffer.buffer; + const buffer_memory_size = base.format.texelSize(attribute.format); + const buffer_memory = if (buffer.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv; + const offset = buffer.interface.offset + (binding_info.stride * invocation_index) + attribute.offset; - const buffer_memory_map: []u8 = @as([*]u8, @ptrCast(@alignCast(try buffer_memory.map(offset, buffer_memory_size))))[0..buffer_memory_size]; + const buffer_memory_map: []u8 = @as([*]u8, @ptrCast(@alignCast(try buffer_memory.map(offset, buffer_memory_size))))[0..buffer_memory_size]; - try rt.writeInput(buffer_memory_map, location_result); + try rt.writeInput(buffer_memory_map, location_result); + } } rt.callEntryPoint(allocator, entry) catch |err| switch (err) { @@ -69,8 +72,21 @@ inline fn run(data: RunData) !void { else => return err, }; - const output: *F32x4 = &data.draw_call.vertices[(data.instance_index * data.vertex_count) + invocation_index]; - try rt.readBuiltIn(std.mem.asBytes(output), .Position); + const output: *Renderer.Vertex = &data.draw_call.vertices[(data.instance_index * data.vertex_count) + invocation_index]; + try rt.readBuiltIn(std.mem.asBytes(&output.position), .Position); + + for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { + const result_word = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, + }; + if (result_word == 0) + continue; + const value = rt.results[result_word].getConstValue() catch continue; + const needed_size = try value.getPlainMemorySize(); + output.outputs[location] = data.allocator.alloc(u8, needed_size) catch return VkError.OutOfDeviceMemory; + try rt.readOutput(output.outputs[location].?, result_word); + } } } diff --git a/src/vulkan/CommandBuffer.zig b/src/vulkan/CommandBuffer.zig index eaa4730..07226d2 100644 --- a/src/vulkan/CommandBuffer.zig +++ b/src/vulkan/CommandBuffer.zig @@ -60,6 +60,7 @@ pub const DispatchTable = struct { reset: *const fn (*Self, vk.CommandBufferResetFlags) VkError!void, resetEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void, setEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void, + setViewport: *const fn (*Self, u32, []const vk.Viewport) VkError!void, waitEvents: *const fn (*Self, []*const Event, vk.PipelineStageFlags, vk.PipelineStageFlags, []const vk.MemoryBarrier, []const vk.BufferMemoryBarrier, []const vk.ImageMemoryBarrier) VkError!void, }; @@ -227,6 +228,10 @@ pub inline fn setEvent(self: *Self, event: *Event, stage: vk.PipelineStageFlags) try self.dispatch_table.setEvent(self, event, stage); } +pub inline fn setViewport(self: *Self, first: u32, viewports: []const vk.Viewport) VkError!void { + try self.dispatch_table.setViewport(self, first, viewports); +} + pub inline fn waitEvents(self: *Self, events: []*const Event, src_stage: vk.PipelineStageFlags, dst_stage: vk.PipelineStageFlags, memory_barriers: []const vk.MemoryBarrier, buffer_barriers: []const vk.BufferMemoryBarrier, image_barriers: []const vk.ImageMemoryBarrier) VkError!void { try self.dispatch_table.waitEvents(self, events, src_stage, dst_stage, memory_barriers, buffer_barriers, image_barriers); } diff --git a/src/vulkan/Pipeline.zig b/src/vulkan/Pipeline.zig index d18957d..0958846 100644 --- a/src/vulkan/Pipeline.zig +++ b/src/vulkan/Pipeline.zig @@ -11,6 +11,18 @@ const PipelineCache = @import("PipelineCache.zig"); const Self = @This(); pub const ObjectType: vk.ObjectType = .pipeline; +const DynamicState = struct { + viewport: bool = false, + scissor: bool = false, + line_width: bool = false, + depth_bias: bool = false, + blend_constants: bool = false, + depth_bounds: bool = false, + stencil_compare_mask: bool = false, + stencil_write_mask: bool = false, + stencil_reference: bool = false, +}; + owner: *Device, vtable: *const VTable, @@ -25,8 +37,8 @@ mode: union(enum) { topology: vk.PrimitiveTopology, }, viewport_state: struct { - viewports: []vk.Viewport, - scissor: []vk.Rect2D, + viewports: ?[]vk.Viewport, + scissor: ?[]vk.Rect2D, }, rasterization: struct { polygon_mode: vk.PolygonMode, @@ -34,6 +46,7 @@ mode: union(enum) { front_face: vk.FrontFace, line_width: f32, }, + dynamic_state: DynamicState, }, }, @@ -101,7 +114,7 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe break :blk allocator.dupe(vk.Viewport, viewports[0..viewport_state.viewport_count]) catch return VkError.OutOfHostMemory; } } - return VkError.ValidationFailed; + break :blk null; }, .scissor = blk: { if (info.p_viewport_state) |viewport_state| { @@ -109,7 +122,7 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe break :blk allocator.dupe(vk.Rect2D, scissors[0..viewport_state.scissor_count]) catch return VkError.OutOfHostMemory; } } - return VkError.ValidationFailed; + break :blk null; }, }, .rasterization = .{ @@ -118,6 +131,30 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .front_face = if (info.p_rasterization_state) |state| state.front_face else return VkError.ValidationFailed, .line_width = if (info.p_rasterization_state) |state| state.line_width else return VkError.ValidationFailed, }, + .dynamic_state = blk: { + var state: DynamicState = .{}; + + if (info.p_dynamic_state) |dynamic_state| { + if (dynamic_state.p_dynamic_states) |states| { + for (states[0..], 0..dynamic_state.dynamic_state_count) |info_state, _| { + switch (info_state) { + .viewport => state.viewport = true, + .scissor => state.scissor = true, + .line_width => state.line_width = true, + .depth_bias => state.depth_bias = true, + .blend_constants => state.blend_constants = true, + .depth_bounds => state.depth_bounds = true, + .stencil_compare_mask => state.stencil_compare_mask = true, + .stencil_write_mask => state.stencil_write_mask = true, + .stencil_reference => state.stencil_reference = true, + else => return VkError.Unknown, + } + } + } + } + + break :blk state; + }, }, }, }; diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 8c629e0..e7bf69e 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -856,7 +856,12 @@ pub export fn strollCreateGraphicsPipelines(p_device: vk.Device, p_cache: vk.Pip // error code. The implementation will attempt to create all pipelines, and // only return VK_NULL_HANDLE values for those that actually failed." p_pipeline.*, const local_res = blk: { - const pipeline = device.createGraphicsPipeline(allocator, cache, info) catch |err| break :blk .{ .null_handle, toVkResult(err) }; + const pipeline = device.createGraphicsPipeline(allocator, cache, info) catch |err| { + if (@errorReturnTrace()) |trace| { + std.debug.dumpErrorReturnTrace(trace); + } + break :blk .{ .null_handle, toVkResult(err) }; + }; const handle = NonDispatchable(Pipeline).wrap(allocator, pipeline) catch |err| { pipeline.destroy(allocator); break :blk .{ .null_handle, toVkResult(err) }; @@ -2133,13 +2138,7 @@ pub export fn strollCmdSetViewport(p_cmd: vk.CommandBuffer, first: u32, count: u defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = first; - _ = count; - _ = viewports; + cmd.setViewport(first, viewports[0..count]) catch |err| return errorLogger(err); } pub export fn strollCmdUpdateBuffer(p_cmd: vk.CommandBuffer, p_buffer: vk.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize, data: *const anyopaque) callconv(vk.vulkan_call_conv) void { diff --git a/src/vulkan/logger.zig b/src/vulkan/logger.zig index 4d3d19c..022e380 100644 --- a/src/vulkan/logger.zig +++ b/src/vulkan/logger.zig @@ -55,12 +55,13 @@ pub fn log(comptime level: std.log.Level, comptime scope: @EnumLiteral(), compti file.lock(io, .exclusive) catch {}; defer file.unlock(io); - const now = std.Io.Timestamp.now(io, .cpu_process).toMilliseconds(); + const now = std.Io.Timestamp.now(io, .cpu_process).toMicroseconds(); - const now_ms: u16 = @intCast(@mod(now, std.time.ms_per_s)); - const now_sec: u8 = @intCast(@mod(@divTrunc(now, std.time.ms_per_s), std.time.s_per_min)); - const now_min: u8 = @intCast(@mod(@divTrunc(now, std.time.ms_per_min), 60)); - const now_hour: u8 = @intCast(@mod(@divTrunc(now, std.time.ms_per_hour), 24)); + const now_us: u16 = @intCast(@mod(now, 1000)); + const now_ms: u16 = @intCast(@mod(@divTrunc(now, 1000), std.time.ms_per_s)); + const now_sec: u8 = @intCast(@mod(@divTrunc(now, std.time.us_per_s), std.time.s_per_min)); + const now_min: u8 = @intCast(@mod(@divTrunc(now, std.time.us_per_min), 60)); + const now_hour: u8 = @intCast(@mod(@divTrunc(now, std.time.us_per_hour), 24)); var fmt_buffer = std.mem.zeroes([4096]u8); var fmt_writer = std.Io.Writer.fixed(&fmt_buffer); @@ -82,15 +83,15 @@ pub fn log(comptime level: std.log.Level, comptime scope: @EnumLiteral(), compti }; term.setColor(.magenta) catch {}; - writer.print("[StrollDriver ", .{}) catch continue; + writer.writeAll("[StrollDriver") catch continue; if (!builtin.is_test) { term.setColor(.cyan) catch {}; - writer.print(root.DRIVER_NAME, .{}) catch continue; + writer.writeAll(" " ++ root.DRIVER_NAME ++ " ") catch continue; } term.setColor(.yellow) catch {}; - writer.print(" {d}:{d}:{d}.{d:0>3}", .{ now_hour, now_min, now_sec, now_ms }) catch continue; + writer.print("{d}:{d}:{d}.{d:0>3}.{d:0>3}", .{ now_hour, now_min, now_sec, now_ms, now_us }) catch continue; term.setColor(.magenta) catch {}; - writer.print("]", .{}) catch continue; + writer.writeAll("]") catch continue; term.setColor(.cyan) catch {}; writer.print("[Thread {d: >8}]", .{std.Thread.getCurrentId()}) catch continue;