Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f8da0940d | ||
|
|
4901dc654d | ||
|
|
e03cb3f285 | ||
|
|
14def5d555 |
@@ -42,11 +42,14 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
const add_cp = try GpuCompute.init(
|
const add_cp = try GpuCompute.init(
|
||||||
gloc,
|
gloc,
|
||||||
@embedFile("shaders/add.wgsl"),
|
@embedFile("shaders/add.wgsl"),
|
||||||
.{ .bindings = &.{
|
.{
|
||||||
.{ .element_size = @sizeOf(f16) },
|
.label = "add",
|
||||||
.{ .element_size = @sizeOf(f16) },
|
.bindings = &.{
|
||||||
.{ .element_size = @sizeOf(f16) },
|
.{ .element_size = @sizeOf(f16) },
|
||||||
} },
|
.{ .element_size = @sizeOf(f16) },
|
||||||
|
.{ .element_size = @sizeOf(f16) },
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 4. Setup CPU data
|
// 4. Setup CPU data
|
||||||
@@ -63,12 +66,13 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
|
|
||||||
// 5. Initialize raw GPU Buffers
|
// 5. Initialize raw GPU Buffers
|
||||||
const byte_size = len * @sizeOf(f16);
|
const byte_size = len * @sizeOf(f16);
|
||||||
const buf_a = try GpuBuffer.init(gloc, byte_size, .initMany(&.{ .Storage, .CopyDst, .CopySrc }));
|
const buf_a = try GpuBuffer.init(gloc, .{ .label = "a", .size = byte_size, .usage = .initMany(&.{ .Storage, .CopyDst, .CopySrc }) });
|
||||||
const buf_b = try GpuBuffer.init(gloc, byte_size, .initMany(&.{ .Storage, .CopyDst, .CopySrc }));
|
const buf_b = try GpuBuffer.init(gloc, .{ .label = "b", .size = byte_size, .usage = .initMany(&.{ .Storage, .CopyDst, .CopySrc }) });
|
||||||
const buf_out = try GpuBuffer.init(gloc, byte_size, .initMany(&.{ .Storage, .CopyDst, .CopySrc }));
|
const buf_out = try GpuBuffer.init(gloc, .{ .label = "out", .size = byte_size, .usage = .initMany(&.{ .Storage, .CopyDst, .CopySrc }) });
|
||||||
|
|
||||||
// Note: Buffers, pipelines, and other objects initialized with 'gloc'
|
// Note: Buffers are safely tied to the GpuArenaAllocator which will automatically
|
||||||
// are safely tied to the GpuArenaAllocator and will automatically release.
|
// release them at the end. You can also manually call buf_x.deinit() if desired.
|
||||||
|
// This will also release pipelines, textures, ect. Everything using a GpuAllocator to init.
|
||||||
|
|
||||||
// 6. Transfer data from CPU slices to GPU Buffers
|
// 6. Transfer data from CPU slices to GPU Buffers
|
||||||
try buf_a.load(f16, data_a);
|
try buf_a.load(f16, data_a);
|
||||||
@@ -96,6 +100,7 @@ and pull the frame pixels back to the CPU to write a standard image file:
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const gpu = @import("gpu");
|
const gpu = @import("gpu");
|
||||||
const GpuDevice = gpu.GpuDevice;
|
const GpuDevice = gpu.GpuDevice;
|
||||||
|
const GpuArenaAllocator = gpu.GpuArenaAllocator;
|
||||||
const GpuBuffer = gpu.GpuBuffer;
|
const GpuBuffer = gpu.GpuBuffer;
|
||||||
const GpuRender = gpu.GpuRender;
|
const GpuRender = gpu.GpuRender;
|
||||||
const GpuTexture = gpu.GpuTexture;
|
const GpuTexture = gpu.GpuTexture;
|
||||||
@@ -111,8 +116,10 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
const device = try GpuDevice.init(.{});
|
const device = try GpuDevice.init(.{});
|
||||||
defer device.deinit();
|
defer device.deinit();
|
||||||
|
|
||||||
// 2. Get base device GPU Allocator
|
// 2. Init VRAM Arena
|
||||||
const gloc = device.gpuAllocator();
|
var grena = GpuArenaAllocator.init(allocator, device.gpuAllocator());
|
||||||
|
defer grena.deinit();
|
||||||
|
const gloc = grena.gpuAllocator();
|
||||||
|
|
||||||
// 3. Load Render Pipeline
|
// 3. Load Render Pipeline
|
||||||
const circle_rp = try GpuRender.init(
|
const circle_rp = try GpuRender.init(
|
||||||
@@ -137,15 +144,16 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
// 6. Run the rendering pipeline
|
// 6. Run the rendering pipeline
|
||||||
try circle_rp.draw(gloc, view, 4, .{});
|
try circle_rp.draw(gloc, view, 4, .{});
|
||||||
|
|
||||||
// 7. Copy Texture into a readable GPU staging buffer
|
// 7. Load Texture into GpuBuffer
|
||||||
const cpu_staging_buf = try texture.buffCopy(gloc);
|
const cpu_staging_cpu = try texture.buffCopy(gloc);
|
||||||
defer cpu_staging_buf.deinit();
|
defer cpu_staging_cpu.deinit();
|
||||||
|
|
||||||
// 8. Read GpuBuffer to CPU memory
|
// 8. Read GpuBuffer to CPU
|
||||||
const pixels = try cpu_staging_buf.read(allocator, u8);
|
// This need to be free manually because CPU memory
|
||||||
|
const pixels = try cpu_staging_cpu.read(allocator, u8);
|
||||||
defer allocator.free(pixels);
|
defer allocator.free(pixels);
|
||||||
|
|
||||||
// 9. Write out to a simple PPM image
|
// 9. Write a simple ppm image
|
||||||
try savePpm(init.io, "circle.ppm", width, height, pixels);
|
try savePpm(init.io, "circle.ppm", width, height, pixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -32,7 +32,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 4. Setup CPU data
|
// 4. Setup CPU data
|
||||||
const len: usize = 16;
|
const len: usize = 1024;
|
||||||
const data_a = try allocator.alloc(f16, len);
|
const data_a = try allocator.alloc(f16, len);
|
||||||
defer allocator.free(data_a);
|
defer allocator.free(data_a);
|
||||||
const data_b = try allocator.alloc(f16, len);
|
const data_b = try allocator.alloc(f16, len);
|
||||||
@@ -61,8 +61,15 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
try add_cp.run(gloc, .{ buf_a, buf_b, buf_out });
|
try add_cp.run(gloc, .{ buf_a, buf_b, buf_out });
|
||||||
|
|
||||||
// 8. Map and copy the resulting buffer back to the CPU
|
// 8. Map and copy the resulting buffer back to the CPU
|
||||||
const out = try buf_out.read(allocator, f16);
|
const staging = try GpuBuffer.init(gloc, .{
|
||||||
|
.size = byte_size,
|
||||||
|
.usage = .initMany(&.{ .MapRead, .CopyDst }),
|
||||||
|
});
|
||||||
|
defer staging.deinit();
|
||||||
|
|
||||||
|
try buf_out.copy(staging);
|
||||||
|
const out = try staging.read(allocator, f16);
|
||||||
defer allocator.free(out);
|
defer allocator.free(out);
|
||||||
|
|
||||||
std.debug.print("Result: {any}\n", .{out});
|
std.debug.print("Result: {any}\n", .{out[0..@min(6, len)]});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,14 @@ pub fn gpuAllocator(self: *@This()) GpuAllocator {
|
|||||||
fn allocBuffer(ctx: *anyopaque, desc: c.WGPUBufferDescriptor) anyerror!c.WGPUBuffer {
|
fn allocBuffer(ctx: *anyopaque, desc: c.WGPUBufferDescriptor) anyerror!c.WGPUBuffer {
|
||||||
const self: *@This() = @ptrCast(@alignCast(ctx));
|
const self: *@This() = @ptrCast(@alignCast(ctx));
|
||||||
try self.tracked_buffers.ensureTotalCapacity(self.tracked_buffers.count() + 1);
|
try self.tracked_buffers.ensureTotalCapacity(self.tracked_buffers.count() + 1);
|
||||||
|
|
||||||
|
const bytes = desc.size;
|
||||||
|
if (bytes > self.child_allocator.device.limits.maxBufferSize)
|
||||||
|
return error.SingleBufferExceedsLimit;
|
||||||
|
|
||||||
|
if (bytes + self.allocated_vram_bytes > self.child_allocator.device.def.vram_bytes_limit)
|
||||||
|
return error.ExceedsVramBudget;
|
||||||
|
|
||||||
const raw = try self.child_allocator.allocBuffer(desc);
|
const raw = try self.child_allocator.allocBuffer(desc);
|
||||||
self.tracked_buffers.putAssumeCapacity(raw, desc);
|
self.tracked_buffers.putAssumeCapacity(raw, desc);
|
||||||
self.allocated_vram_bytes += desc.size;
|
self.allocated_vram_bytes += desc.size;
|
||||||
|
|||||||
+39
-31
@@ -4,11 +4,10 @@ const GpuAllocator = @import("GpuAllocator.zig");
|
|||||||
const svOpt = @import("utils.zig").svOpt;
|
const svOpt = @import("utils.zig").svOpt;
|
||||||
|
|
||||||
raw: c.WGPUBuffer,
|
raw: c.WGPUBuffer,
|
||||||
size: u64,
|
|
||||||
usage: c.WGPUBufferUsage,
|
|
||||||
gloc: GpuAllocator,
|
gloc: GpuAllocator,
|
||||||
|
def: GpuBufferDef,
|
||||||
|
|
||||||
const BufferUsage = enum(u64) {
|
pub const GpuBufferUsage = enum(u64) {
|
||||||
None = 0x0000000000000000,
|
None = 0x0000000000000000,
|
||||||
MapRead = 0x0000000000000001,
|
MapRead = 0x0000000000000001,
|
||||||
MapWrite = 0x0000000000000002,
|
MapWrite = 0x0000000000000002,
|
||||||
@@ -20,31 +19,34 @@ const BufferUsage = enum(u64) {
|
|||||||
Storage = 0x0000000000000080,
|
Storage = 0x0000000000000080,
|
||||||
Indirect = 0x0000000000000100,
|
Indirect = 0x0000000000000100,
|
||||||
QueryResolve = 0x0000000000000200,
|
QueryResolve = 0x0000000000000200,
|
||||||
|
|
||||||
|
fn enumSetToWGPUBufferUsage(set: std.EnumSet(GpuBufferUsage)) c.WGPUBufferUsage {
|
||||||
|
var use: u64 = 0;
|
||||||
|
var iter = set.iterator();
|
||||||
|
while (iter.next()) |flag| use |= @intFromEnum(flag);
|
||||||
|
return use;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const GpuBufferDef = struct {
|
pub const GpuBufferDef = struct {
|
||||||
label: ?[]const u8 = null,
|
label: ?[]const u8 = null,
|
||||||
size: u64,
|
size: u64,
|
||||||
usage: std.EnumSet(BufferUsage),
|
usage: std.EnumSet(GpuBufferUsage),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(gloc: GpuAllocator, def: GpuBufferDef) !@This() {
|
pub fn init(gloc: GpuAllocator, def: GpuBufferDef) !@This() {
|
||||||
var use: u64 = 0;
|
|
||||||
var iter = def.usage.iterator();
|
|
||||||
while (iter.next()) |flag| use |= @intFromEnum(flag);
|
|
||||||
|
|
||||||
// Automatically align the buffer size forward to a multiple of 4 bytes under the hood
|
// Automatically align the buffer size forward to a multiple of 4 bytes under the hood
|
||||||
const aligned_size = std.mem.alignForward(u64, def.size, 4);
|
const aligned_size = std.mem.alignForward(u64, def.size, 4);
|
||||||
|
|
||||||
const raw_handle = try gloc.allocBuffer(.{
|
const raw_handle = try gloc.allocBuffer(.{
|
||||||
.size = aligned_size,
|
.size = aligned_size,
|
||||||
.usage = use,
|
.usage = GpuBufferUsage.enumSetToWGPUBufferUsage(def.usage),
|
||||||
.label = svOpt(def.label),
|
.label = svOpt(def.label),
|
||||||
});
|
});
|
||||||
return .{
|
return .{
|
||||||
.raw = raw_handle,
|
.raw = raw_handle,
|
||||||
.size = aligned_size,
|
.def = def,
|
||||||
.usage = use,
|
|
||||||
.gloc = gloc,
|
.gloc = gloc,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -79,9 +81,9 @@ pub fn load(
|
|||||||
) !void {
|
) !void {
|
||||||
const bytes = data.len * @sizeOf(T);
|
const bytes = data.len * @sizeOf(T);
|
||||||
|
|
||||||
if (bytes == self.size) {
|
if (bytes == self.def.size) {
|
||||||
// Aligned path: direct download
|
// Aligned path: direct download
|
||||||
c.wgpuQueueWriteBuffer(self.gloc.device.queue, self.raw, 0, data.ptr, self.size);
|
c.wgpuQueueWriteBuffer(self.gloc.device.queue, self.raw, 0, data.ptr, self.def.size);
|
||||||
} else {
|
} else {
|
||||||
// Unaligned path: Split the write into an aligned chunk and a padded remainder
|
// Unaligned path: Split the write into an aligned chunk and a padded remainder
|
||||||
// to support arbitrary lengths without any allocations or large stack arrays.
|
// to support arbitrary lengths without any allocations or large stack arrays.
|
||||||
@@ -99,37 +101,26 @@ pub fn load(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// GPU to CPU
|
/// GPU to CPU
|
||||||
|
/// Buffer must have MapRead usage or returns error.BufferNotMappable.
|
||||||
pub fn read(self: @This(), alloc: std.mem.Allocator, T: type) ![]T {
|
pub fn read(self: @This(), alloc: std.mem.Allocator, T: type) ![]T {
|
||||||
const out = try alloc.alloc(T, @divExact(self.size, @sizeOf(T)));
|
if (!self.def.usage.contains(.MapRead)) return error.BufferNotMappable;
|
||||||
|
|
||||||
const staging = try init(self.gloc, .{
|
const out = try alloc.alloc(T, @divExact(self.def.size, @sizeOf(T)));
|
||||||
.size = self.size,
|
|
||||||
.usage = .initMany(&.{ .MapRead, .CopyDst }),
|
|
||||||
.label = "staging_read_buffer",
|
|
||||||
});
|
|
||||||
defer staging.deinit();
|
|
||||||
|
|
||||||
const enc = c.wgpuDeviceCreateCommandEncoder(self.gloc.device.device, null) orelse return error.Encoder;
|
|
||||||
c.wgpuCommandEncoderCopyBufferToBuffer(enc, self.raw, 0, staging.raw, 0, self.size);
|
|
||||||
const cmd = c.wgpuCommandEncoderFinish(enc, null);
|
|
||||||
defer c.wgpuCommandEncoderRelease(enc);
|
|
||||||
defer c.wgpuCommandBufferRelease(cmd);
|
|
||||||
c.wgpuQueueSubmit(self.gloc.device.queue, 1, &cmd);
|
|
||||||
|
|
||||||
var mapped = false;
|
var mapped = false;
|
||||||
staging.mapAsync(
|
self.mapAsync(
|
||||||
c.WGPUMapMode_Read,
|
c.WGPUMapMode_Read,
|
||||||
0,
|
0,
|
||||||
self.size,
|
self.def.size,
|
||||||
.{ .callback = onMapped, .userdata1 = &mapped },
|
.{ .callback = onMapped, .userdata1 = &mapped },
|
||||||
);
|
);
|
||||||
while (!mapped) self.gloc.device.poll();
|
while (!mapped) self.gloc.device.poll();
|
||||||
|
|
||||||
const ptr: [*]const T = @ptrCast(@alignCast(
|
const ptr: [*]const T = @ptrCast(@alignCast(
|
||||||
staging.getConstMappedRange(0, self.size),
|
self.getConstMappedRange(0, self.def.size),
|
||||||
));
|
));
|
||||||
@memcpy(out[0..out.len], ptr[0..out.len]);
|
@memcpy(out[0..out.len], ptr[0..out.len]);
|
||||||
staging.unmap();
|
self.unmap();
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -143,3 +134,20 @@ fn onMapped(
|
|||||||
const flag: *bool = @ptrCast(@alignCast(userdata1.?));
|
const flag: *bool = @ptrCast(@alignCast(userdata1.?));
|
||||||
flag.* = (status == c.WGPUMapAsyncStatus_Success);
|
flag.* = (status == c.WGPUMapAsyncStatus_Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GPU to GPU. Both buffers must be same size, src needs CopySrc, dst needs CopyDst.
|
||||||
|
pub fn copy(src: @This(), dst: @This()) !void {
|
||||||
|
if (src.def.size != dst.def.size) return error.SizeMismatch;
|
||||||
|
|
||||||
|
const copy_src: u64 = @intFromEnum(GpuBufferUsage.CopySrc);
|
||||||
|
const copy_dst: u64 = @intFromEnum(GpuBufferUsage.CopyDst);
|
||||||
|
if (@as(u64, GpuBufferUsage.enumSetToWGPUBufferUsage(src.def.usage)) & copy_src == 0) return error.SrcNotCopyable;
|
||||||
|
if (@as(u64, GpuBufferUsage.enumSetToWGPUBufferUsage(dst.def.usage)) & copy_dst == 0) return error.DstNotWritable;
|
||||||
|
|
||||||
|
const enc = c.wgpuDeviceCreateCommandEncoder(src.gloc.device.device, null) orelse return error.Encoder;
|
||||||
|
c.wgpuCommandEncoderCopyBufferToBuffer(enc, src.raw, 0, dst.raw, 0, src.def.size);
|
||||||
|
const cmd = c.wgpuCommandEncoderFinish(enc, null);
|
||||||
|
defer c.wgpuCommandEncoderRelease(enc);
|
||||||
|
defer c.wgpuCommandBufferRelease(cmd);
|
||||||
|
c.wgpuQueueSubmit(src.gloc.device.queue, 1, &cmd);
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -74,7 +74,7 @@ pub fn run(
|
|||||||
const buf = @field(args, field.name);
|
const buf = @field(args, field.name);
|
||||||
const el_size = self.def.bindings[i].element_size;
|
const el_size = self.def.bindings[i].element_size;
|
||||||
if (el_size > 0) {
|
if (el_size > 0) {
|
||||||
elements_count = @intCast(buf.size / el_size);
|
elements_count = @intCast(buf.def.size / el_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ pub fn run(
|
|||||||
const el_size = self.def.bindings[i].element_size;
|
const el_size = self.def.bindings[i].element_size;
|
||||||
if (el_size > 0) {
|
if (el_size > 0) {
|
||||||
const expected_min_bytes = @as(u64, elements_count) * el_size;
|
const expected_min_bytes = @as(u64, elements_count) * el_size;
|
||||||
if (buf.size < expected_min_bytes)
|
if (buf.def.size < expected_min_bytes)
|
||||||
return error.BufferTooSmall;
|
return error.BufferTooSmall;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ pub fn run(
|
|||||||
.binding = @intCast(i),
|
.binding = @intCast(i),
|
||||||
.buffer = buf.raw,
|
.buffer = buf.raw,
|
||||||
.offset = 0,
|
.offset = 0,
|
||||||
.size = buf.size, // Size exposes the fully allocated length
|
.size = buf.def.size, // Size exposes the fully allocated length
|
||||||
};
|
};
|
||||||
entry_count += 1;
|
entry_count += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user