Create a GpuTextureView

This commit is contained in:
adrien 2026-05-20 10:56:35 +02:00
parent 45c0f3180e
commit 545c4b98e9
9 changed files with 99 additions and 34 deletions

View File

@ -6,6 +6,8 @@ pub const VTable = struct {
freeBuffer: *const fn (ctx: *anyopaque, buf_raw: c.WGPUBuffer) void,
allocTexture: *const fn (ctx: *anyopaque, desc: c.WGPUTextureDescriptor) anyerror!c.WGPUTexture,
freeTexture: *const fn (ctx: *anyopaque, buf_raw: c.WGPUTexture) void,
allocTextureView: *const fn (ctx: *anyopaque, texture: c.WGPUTexture, desc: c.WGPUTextureViewDescriptor) anyerror!c.WGPUTextureView,
freeTextureView: *const fn (ctx: *anyopaque, buf_raw: c.WGPUTextureView) void,
};
device: GpuDevice,
@ -27,3 +29,11 @@ pub fn allocTexture(self: @This(), desc: c.WGPUTextureDescriptor) !c.WGPUTexture
pub fn freeTexture(self: @This(), buf_raw: c.WGPUTexture) void {
self.vtable.freeTexture(self.ptr, buf_raw);
}
pub fn allocTextureView(self: @This(), texture: c.WGPUTexture, desc: c.WGPUTextureViewDescriptor) !c.WGPUTextureView {
return self.vtable.allocTextureView(self.ptr, texture, desc);
}
pub fn freeTextureView(self: @This(), buf_raw: c.WGPUTextureView) void {
self.vtable.freeTextureView(self.ptr, buf_raw);
}

View File

@ -7,6 +7,7 @@ const c = @import("utils.zig").c;
device: GpuDevice,
tracked_buffers: std.AutoHashMap(c.WGPUBuffer, c.WGPUBufferDescriptor),
tracked_textures: std.AutoHashMap(c.WGPUTexture, c.WGPUTextureDescriptor),
tracked_views: std.AutoHashMap(c.WGPUTextureView, c.WGPUTextureViewDescriptor),
allocated_vram_bytes: u64 = 0,
pub fn init(cpu_allocator: std.mem.Allocator, device: GpuDevice) @This() {
@ -14,6 +15,7 @@ pub fn init(cpu_allocator: std.mem.Allocator, device: GpuDevice) @This() {
.device = device,
.tracked_buffers = .init(cpu_allocator),
.tracked_textures = .init(cpu_allocator),
.tracked_views = .init(cpu_allocator),
};
}
@ -29,6 +31,11 @@ pub fn deinit(self: *@This()) void {
while (it_texture.next()) |tex_ptr|
c.wgpuTextureRelease(tex_ptr.*);
self.tracked_textures.deinit();
var it_view = self.tracked_views.keyIterator();
while (it_view.next()) |view_ptr|
c.wgpuTextureViewRelease(view_ptr.*);
self.tracked_views.deinit();
}
/// Returns the type-erased immutable interface wrapper
@ -41,6 +48,8 @@ pub fn gpuAllocator(self: *@This()) GpuAllocator {
.freeBuffer = freeBuffer,
.allocTexture = allocTexture,
.freeTexture = freeTexture,
.allocTextureView = allocTextureView,
.freeTextureView = freeTextureView,
},
};
}
@ -65,12 +74,12 @@ fn allocBuffer(ctx: *anyopaque, desc: c.WGPUBufferDescriptor) anyerror!c.WGPUBuf
return buf;
}
fn freeBuffer(ctx: *anyopaque, buf_raw: c.WGPUBuffer) void {
fn freeBuffer(ctx: *anyopaque, raw: c.WGPUBuffer) void {
const self: *@This() = @ptrCast(@alignCast(ctx));
if (self.tracked_buffers.fetchRemove(buf_raw)) |kv| {
c.wgpuBufferDestroy(buf_raw);
c.wgpuBufferRelease(buf_raw);
if (self.tracked_buffers.fetchRemove(raw)) |kv| {
c.wgpuBufferDestroy(raw);
c.wgpuBufferRelease(raw);
self.allocated_vram_bytes -= kv.value.size;
}
}
@ -93,11 +102,11 @@ fn allocTexture(ctx: *anyopaque, desc: c.WGPUTextureDescriptor) anyerror!c.WGPUT
return texture;
}
fn freeTexture(ctx: *anyopaque, texture_raw: c.WGPUTexture) void {
fn freeTexture(ctx: *anyopaque, raw: c.WGPUTexture) void {
const self: *@This() = @ptrCast(@alignCast(ctx));
if (self.tracked_textures.fetchRemove(texture_raw)) |kv| {
c.wgpuTextureRelease(texture_raw);
if (self.tracked_textures.fetchRemove(raw)) |kv| {
c.wgpuTextureRelease(raw);
const desc = kv.value;
const format: GpuTextureFormat = @enumFromInt(desc.format);
@ -105,3 +114,16 @@ fn freeTexture(ctx: *anyopaque, texture_raw: c.WGPUTexture) void {
self.allocated_vram_bytes -= bytes_size;
}
}
fn allocTextureView(ctx: *anyopaque, texture: c.WGPUTexture, desc: c.WGPUTextureViewDescriptor) anyerror!c.WGPUTextureView {
const self: *@This() = @ptrCast(@alignCast(ctx));
const view = c.wgpuTextureCreateView(texture, &desc) orelse return error.View;
try self.tracked_views.put(view, desc);
return view;
}
fn freeTextureView(ctx: *anyopaque, raw: c.WGPUTextureView) void {
const self: *@This() = @ptrCast(@alignCast(ctx));
if (self.tracked_views.remove(raw))
c.wgpuTextureViewRelease(raw);
}

View File

@ -21,7 +21,6 @@ const BufferUsage = enum(u64) {
QueryResolve = 0x0000000000000200,
};
/// Allocates the underlying WebGPU handle and registers it to the parent GpuAllocator
pub fn init(gloc: GpuAllocator, size: u64, usage: std.EnumSet(BufferUsage)) !@This() {
var use: u64 = 0;
var iter = usage.iterator();
@ -39,17 +38,14 @@ pub fn init(gloc: GpuAllocator, size: u64, usage: std.EnumSet(BufferUsage)) !@Th
};
}
/// Unregisters from the parent GpuAllocator and cleanly destroys GPU resources
pub fn deinit(self: @This()) void {
self.gloc.freeBuffer(self.raw);
}
/// Native getConstMappedRange wrapper
pub fn getConstMappedRange(self: @This(), offset: u64, size: u64) ?*const anyopaque {
return c.wgpuBufferGetConstMappedRange(self.raw, offset, size);
}
/// Native mapAsync wrapper
pub fn mapAsync(
self: @This(),
mode: c.WGPUMapMode,
@ -60,12 +56,11 @@ pub fn mapAsync(
_ = c.wgpuBufferMapAsync(self.raw, mode, offset, size, callback_info);
}
/// Native unmap wrapper
pub fn unmap(self: @This()) void {
c.wgpuBufferUnmap(self.raw);
}
/// CPU to GPU.
/// CPU to GPU
pub fn load(
self: @This(),
T: type,
@ -92,6 +87,7 @@ pub fn load(
}
}
/// GPU to CPU
pub fn read(self: @This(), alloc: std.mem.Allocator, T: type) ![]T {
const out = try alloc.alloc(T, @divExact(self.size, @sizeOf(T)));

View File

@ -2,6 +2,11 @@ const std = @import("std");
const c = @import("utils.zig").c;
const sv = @import("utils.zig").sv;
// TODO: Make Allocator more zig like
// - GpuDevice can return a GpuAllocator that just allocate and nothing else
// - From this GpuAllocator, can create a GpuArena like std.heap.ArenaAllocator.init(allocator)
// - Rename GpuArenaAllocator too
const Ctx = struct {
adapter: c.WGPUAdapter = null,
device: c.WGPUDevice = null,

View File

@ -4,6 +4,7 @@ const sv = @import("utils.zig").sv;
const GpuAllocator = @import("GpuAllocator.zig");
const GpuBuffer = @import("GpuBuffer.zig");
const GpuDevice = @import("GpuDevice.zig");
const GpuTextureView = @import("GpuTextureView.zig");
const GpuTextureFormat = @import("lib.zig").GpuTextureFormat;
pub const Binding = struct {
@ -99,7 +100,7 @@ pub fn deinit(self: @This()) void {
pub fn draw(
self: @This(),
gloc: GpuAllocator,
target_view: c.WGPUTextureView,
target_view: GpuTextureView,
vertex_count: u32,
args: anytype,
) !void {
@ -144,7 +145,7 @@ pub fn draw(
defer c.wgpuCommandEncoderRelease(enc);
const color_attachment = c.WGPURenderPassColorAttachment{
.view = target_view,
.view = target_view.raw,
.resolveTarget = null,
.loadOp = c.WGPULoadOp_Clear,
.storeOp = c.WGPUStoreOp_Store,

View File

@ -2,16 +2,7 @@ const std = @import("std");
const c = @import("utils.zig").c;
const GpuAllocator = @import("GpuAllocator.zig");
const GpuTextureFormat = @import("lib.zig").GpuTextureFormat;
const GpuTextureUsage = enum(u64) {
None = 0x0000000000000000,
CopySrc = 0x0000000000000001,
CopyDst = 0x0000000000000002,
TextureBinding = 0x0000000000000004,
StorageBinding = 0x0000000000000008,
RenderAttachment = 0x0000000000000010,
TransientAttachment = 0x0000000000000020,
};
const GpuTextureUsage = @import("lib.zig").GpuTextureUsage;
pub const GpuTextureDef = struct {
size: c.WGPUExtent3D,
@ -23,7 +14,6 @@ raw: c.WGPUTexture,
gloc: GpuAllocator,
def: GpuTextureDef,
/// Allocates the underlying WebGPU handle and registers it to the parent GpuAllocator
pub fn init(gloc: GpuAllocator, def: GpuTextureDef) !@This() {
var use: u64 = 0;
var iter = def.usage.iterator();
@ -42,17 +32,14 @@ pub fn init(gloc: GpuAllocator, def: GpuTextureDef) !@This() {
return .{ .gloc = gloc, .raw = raw, .def = def };
}
/// Unregisters from the parent GpuAllocator and cleanly destroys GPU resources
pub fn deinit(self: @This()) void {
self.gloc.freeTexture(self.raw);
}
/// Native getConstMappedRange wrapper
pub fn getConstMappedRange(self: @This(), offset: u64, size: u64) ?*const anyopaque {
return c.wgpuBufferGetConstMappedRange(self.raw, offset, size);
}
/// Native mapAsync wrapper
pub fn mapAsync(
self: @This(),
mode: c.WGPUMapMode,
@ -63,12 +50,11 @@ pub fn mapAsync(
_ = c.wgpuBufferMapAsync(self.raw, mode, offset, size, callback_info);
}
/// Native unmap wrapper
pub fn unmap(self: @This()) void {
c.wgpuBufferUnmap(self.raw);
}
/// CPU to GPU.
/// CPU to GPU
pub fn load(
self: @This(),
T: type,
@ -95,6 +81,7 @@ pub fn load(
}
}
// GPU to CPU
pub fn read(self: @This(), alloc: std.mem.Allocator, T: type) ![]T {
const out = try alloc.alloc(T, @divExact(self.size, @sizeOf(T)));

32
src/GpuTextureView.zig Normal file
View File

@ -0,0 +1,32 @@
const std = @import("std");
const c = @import("utils.zig").c;
const GpuAllocator = @import("GpuAllocator.zig");
const GpuTexture = @import("lib.zig").GpuTexture;
const GpuTextureFormat = @import("lib.zig").GpuTextureFormat;
const GpuTextureUsage = @import("lib.zig").GpuTextureUsage;
pub const GpuViewDef = struct {
usage: std.EnumSet(GpuTextureUsage) = .empty,
format: GpuTextureFormat = .Undefined,
};
raw: c.WGPUTextureView,
gloc: GpuAllocator,
pub fn init(gloc: GpuAllocator, texture: GpuTexture, def: GpuViewDef) !@This() {
var use: u64 = 0;
var iter = def.usage.iterator();
while (iter.next()) |flag| use |= @intFromEnum(flag);
const raw = try gloc.allocTextureView(texture.raw, .{
.format = @intFromEnum(def.format),
.usage = use,
.mipLevelCount = 1,
.arrayLayerCount = 1,
});
return .{ .gloc = gloc, .raw = raw };
}
pub fn deinit(self: @This()) void {
self.gloc.freeTextureView(self.raw);
}

View File

@ -7,6 +7,7 @@ const GpuArena = gpu.GpuArena;
const GpuBuffer = gpu.GpuBuffer;
const GpuRender = gpu.GpuRender;
const GpuTexture = gpu.GpuTexture;
const GpuTextureView = gpu.GpuTextureView;
pub fn main(init: std.process.Init) !void {
const allocator = init.gpa;
@ -42,8 +43,8 @@ pub fn main(init: std.process.Init) !void {
});
defer texture.deinit();
const target_view = c.wgpuTextureCreateView(texture.raw, null) orelse return error.View;
defer c.wgpuTextureViewRelease(target_view);
const view = try GpuTextureView.init(gloc, texture, .{});
defer view.deinit();
// 4. Create a staging buffer to pull pixels from VRAM to CPU
// 4 bytes per pixel (RGBA8)
@ -54,7 +55,7 @@ pub fn main(init: std.process.Init) !void {
const cpu_staging_buf = try GpuBuffer.init(gloc, buffer_bytes, .initMany(&.{ .CopyDst, .CopySrc }));
// 5. Draw the Circle Frame into the texture view!
try circle_rp.draw(gloc, target_view, 4, .{});
try circle_rp.draw(gloc, view, 4, .{});
// 6. Copy the texture data into our CPU staging buffer
const enc = c.wgpuDeviceCreateCommandEncoder(device.device, null) orelse return error.Encoder;

View File

@ -5,6 +5,7 @@ pub const GpuDevice = @import("GpuDevice.zig");
pub const GpuCompute = @import("GpuCompute.zig");
pub const GpuRender = @import("GpuRender.zig");
pub const GpuTexture = @import("GpuTexture.zig");
pub const GpuTextureView = @import("GpuTextureView.zig");
pub const GpuTextureFormat = enum(c_uint) {
Undefined = 0,
@ -174,3 +175,13 @@ pub const GpuTextureFormat = enum(c_uint) {
};
}
};
pub const GpuTextureUsage = enum(u64) {
None = 0x0000000000000000,
CopySrc = 0x0000000000000001,
CopyDst = 0x0000000000000002,
TextureBinding = 0x0000000000000004,
StorageBinding = 0x0000000000000008,
RenderAttachment = 0x0000000000000010,
TransientAttachment = 0x0000000000000020,
};