From e5f48580baff1715d3bbe04d45fafc4293512336 Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Fri, 27 Dec 2024 16:38:42 -0700 Subject: [PATCH] gfx: minor Sprite module cleanup/improvements Signed-off-by: Stephen Gutekanst --- examples/glyphs/App.zig | 23 ++++++++--------- examples/sprite/App.zig | 25 +++++++++---------- src/gfx/Sprite.zig | 55 ++++++++++++++++++++--------------------- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/examples/glyphs/App.zig b/examples/glyphs/App.zig index 43ac2f53..759ed184 100644 --- a/examples/glyphs/App.zig +++ b/examples/glyphs/App.zig @@ -124,7 +124,7 @@ fn setupPipeline( // Create our player sprite const r = app.regions.get('?').?; - app.player_id = try sprite.sprites.new(.{ + app.player_id = try sprite.objects.new(.{ .transform = Mat4x4.translate(vec3(-0.02, 0, 0)), .size = vec2(@floatFromInt(r.width), @floatFromInt(r.height)), .uv_transform = Mat3x3.translate(vec2(@floatFromInt(r.x), @floatFromInt(r.y))), @@ -223,8 +223,8 @@ pub fn tick( app.direction = direction; app.spawning = spawning; - var player = sprite.sprites.getValue(app.player_id); - defer sprite.sprites.setValue(app.player_id, player); + var player = sprite.objects.getValue(app.player_id); + defer sprite.objects.setValue(app.player_id, player); var player_pos = player.transform.translation(); if (spawning and app.spawn_timer.read() > 1.0 / 60.0) { // Spawn new entities @@ -237,7 +237,7 @@ pub fn tick( const rand_index = app.rand.random().intRangeAtMost(usize, 0, app.regions.count() - 1); const r = app.regions.entries.get(rand_index).value; - const new_sprite_id = try sprite.sprites.new(.{ + const new_sprite_id = try sprite.objects.new(.{ .transform = Mat4x4.translate(new_pos).mul(&Mat4x4.scaleScalar(0.3)), .size = vec2(@floatFromInt(r.width), @floatFromInt(r.height)), .uv_transform = Mat3x3.translate(vec2(@floatFromInt(r.x), @floatFromInt(r.y))), @@ -256,14 +256,14 @@ pub fn tick( var pipeline_children = try sprite.pipelines.getChildren(app.pipeline_id); defer pipeline_children.deinit(); for (pipeline_children.items) |sprite_id| { - if (!sprite.sprites.is(sprite_id)) continue; + if (!sprite.objects.is(sprite_id)) continue; if (sprite_id == app.player_id) continue; // don't rotate the player - var s = sprite.sprites.getValue(sprite_id); + var s = sprite.objects.getValue(sprite_id); const location = s.transform.translation(); if (location.x() < -@as(f32, @floatFromInt(window.width)) / 1.5 or location.x() > @as(f32, @floatFromInt(window.width)) / 1.5 or location.y() < -@as(f32, @floatFromInt(window.height)) / 1.5 or location.y() > @as(f32, @floatFromInt(window.height)) / 1.5) { - try sprite.sprites.setParent(sprite_id, null); - sprite.sprites.delete(sprite_id); + try sprite.objects.setParent(sprite_id, null); + sprite.objects.delete(sprite_id); app.sprites -= 1; continue; } @@ -274,7 +274,7 @@ pub fn tick( transform = transform.mul(&Mat4x4.rotateZ(2 * math.pi * app.time)); transform = transform.mul(&Mat4x4.scale(Vec3.splat(@max(math.cos(app.time / 2.0), 0.2)))); s.transform = transform; - sprite.sprites.setValue(sprite_id, s); + sprite.objects.setValue(sprite_id, s); } // Calculate the player position, by moving in the direction the player wants to go @@ -317,6 +317,9 @@ pub fn tick( command.release(); render_pass.release(); + app.frame_count += 1; + app.time += delta_time; + // TODO(object): window-title // // Every second, update the window title with the FPS // if (app.fps_timer.read() >= 1.0) { @@ -329,6 +332,4 @@ pub fn tick( // app.fps_timer.reset(); // app.frame_count = 0; // } - app.frame_count += 1; - app.time += delta_time; } diff --git a/examples/sprite/App.zig b/examples/sprite/App.zig index 155d536b..d5876a8c 100644 --- a/examples/sprite/App.zig +++ b/examples/sprite/App.zig @@ -44,9 +44,7 @@ pub fn init( core: *mach.Core, app: *App, app_mod: mach.Mod(App), - sprite: *gfx.Sprite, ) !void { - _ = sprite; // autofix core.on_tick = app_mod.id.tick; core.on_exit = app_mod.id.deinit; @@ -83,7 +81,7 @@ fn setupPipeline( }); // Create our player sprite - app.player_id = try sprite.sprites.new(.{ + app.player_id = try sprite.objects.new(.{ .transform = Mat4x4.translate(vec3(-0.02, 0, 0)), .size = vec2(32, 32), .uv_transform = Mat3x3.translate(vec2(0, 0)), @@ -132,8 +130,7 @@ pub fn tick( app.direction = direction; app.spawning = spawning; - var player = sprite.sprites.getValue(app.player_id); - defer sprite.sprites.setValue(app.player_id, player); + var player = sprite.objects.getValue(app.player_id); var player_pos = player.transform.translation(); if (spawning and app.spawn_timer.read() > 1.0 / 60.0) { // Spawn new entities @@ -143,7 +140,7 @@ pub fn tick( new_pos.v[0] += app.rand.random().floatNorm(f32) * 25; new_pos.v[1] += app.rand.random().floatNorm(f32) * 25; - const new_sprite_id = try sprite.sprites.new(.{ + const new_sprite_id = try sprite.objects.new(.{ .transform = Mat4x4.translate(new_pos).mul(&Mat4x4.scale(Vec3.splat(0.3))), .size = vec2(32, 32), .uv_transform = Mat3x3.translate(vec2(0, 0)), @@ -160,16 +157,15 @@ pub fn tick( var pipeline_children = try sprite.pipelines.getChildren(app.pipeline_id); defer pipeline_children.deinit(); for (pipeline_children.items) |sprite_id| { - if (!sprite.sprites.is(sprite_id)) continue; + if (!sprite.objects.is(sprite_id)) continue; if (sprite_id == app.player_id) continue; // don't rotate the player - var s = sprite.sprites.getValue(sprite_id); - defer sprite.sprites.setValue(sprite_id, s); + var s = sprite.objects.getValue(sprite_id); const location = s.transform.translation(); var transform = Mat4x4.ident; transform = transform.mul(&Mat4x4.translate(location)); transform = transform.mul(&Mat4x4.rotateZ(2 * math.pi * app.time)); transform = transform.mul(&Mat4x4.scaleScalar(@min(math.cos(app.time / 2.0), 0.5))); - s.transform = transform; + sprite.objects.set(sprite_id, .transform, transform); } // Calculate the player position, by moving in the direction the player wants to go @@ -177,7 +173,7 @@ pub fn tick( const speed = 200.0; player_pos.v[0] += direction.x() * speed * delta_time; player_pos.v[1] += direction.y() * speed * delta_time; - player.transform = Mat4x4.translate(player_pos); + sprite.objects.set(app.player_id, .transform, Mat4x4.translate(player_pos)); const window = core.windows.getValue(app.window); @@ -214,6 +210,9 @@ pub fn tick( command.release(); render_pass.release(); + app.frame_count += 1; + app.time += delta_time; + // TODO(object): window-title // // Every second, update the window title with the FPS // if (app.fps_timer.read() >= 1.0) { @@ -226,8 +225,6 @@ pub fn tick( // app.fps_timer.reset(); // app.frame_count = 0; // } - app.frame_count += 1; - app.time += delta_time; } pub fn deinit( @@ -235,7 +232,7 @@ pub fn deinit( sprite: *gfx.Sprite, ) void { // Cleanup here, if desired. - sprite.sprites.delete(app.player_id); + sprite.objects.delete(app.player_id); } // TODO(sprite): don't require users to copy / write this helper themselves diff --git a/src/gfx/Sprite.zig b/src/gfx/Sprite.zig index b0385ff5..c0f36aba 100644 --- a/src/gfx/Sprite.zig +++ b/src/gfx/Sprite.zig @@ -19,6 +19,8 @@ pub const mach_module = .mach_gfx_sprite; pub const mach_systems = .{.tick}; +// TODO(sprite): currently not handling deinit properly + const Uniforms = extern struct { /// The view * orthographic projection matrix view_projection: math.Mat4x4 align(16), @@ -27,7 +29,7 @@ const Uniforms = extern struct { texture_size: math.Vec2 align(16), }; -pub const BuiltPipeline = struct { +const BuiltPipeline = struct { render: *gpu.RenderPipeline, texture_sampler: *gpu.Sampler, texture: *gpu.Texture, @@ -42,7 +44,7 @@ pub const BuiltPipeline = struct { uv_transforms: *gpu.Buffer, sizes: *gpu.Buffer, - pub fn deinit(p: *const BuiltPipeline) void { + fn deinit(p: *const BuiltPipeline) void { p.render.release(); p.texture_sampler.release(); p.texture.release(); @@ -57,14 +59,14 @@ pub const BuiltPipeline = struct { } }; -const sprite_buffer_cap = 1024 * 512; // TODO(sprite): allow user to specify preallocation +const buffer_cap = 1024 * 512; // TODO(sprite): allow user to specify preallocation -pub var cp_transforms: [sprite_buffer_cap]math.Mat4x4 = undefined; +var cp_transforms: [buffer_cap]math.Mat4x4 = undefined; // TODO(d3d12): uv_transform should be a Mat3x3 but our D3D12/HLSL backend cannot handle it. -pub var cp_uv_transforms: [sprite_buffer_cap]math.Mat4x4 = undefined; -pub var cp_sizes: [sprite_buffer_cap]math.Vec2 = undefined; +var cp_uv_transforms: [buffer_cap]math.Mat4x4 = undefined; +var cp_sizes: [buffer_cap]math.Vec2 = undefined; -sprites: mach.Objects(.{ .track_fields = true }, struct { +objects: mach.Objects(.{ .track_fields = true }, struct { /// The sprite model transformation matrix. A sprite is measured in pixel units, starting from /// (0, 0) at the top-left corner and extending to the size of the sprite. By default, the world /// origin (0, 0) lives at the center of the window. @@ -93,11 +95,9 @@ pipelines: mach.Objects(.{ .track_fields = true }, struct { /// Must be specified for a pipeline entity to be valid. texture: *gpu.Texture, - /// View*Projection matrix to use when rendering text with this pipeline. This controls both + /// View*Projection matrix to use when rendering with this pipeline. This controls both /// the size of the 'virtual canvas' which is rendered onto, as well as the 'camera position'. /// - /// This should be configured before .pre_render runs. - /// /// By default, the size is configured to be equal to the window size in virtual pixels (e.g. /// if the window size is 1920x1080, the virtual canvas will also be that size even if ran on a /// HiDPI / Retina display where the actual framebuffer is larger than that.) The origin (0, 0) @@ -115,7 +115,6 @@ pipelines: mach.Objects(.{ .track_fields = true }, struct { /// .far = 100000, /// }); /// const view_projection = projection.mul(&Mat4x4.translate(vec3(0, 0, 0))); - /// try sprite_pipeline.set(my_sprite_pipeline, .view_projection, view_projection); /// ``` view_projection: ?Mat4x4 = null, @@ -159,7 +158,7 @@ pipelines: mach.Objects(.{ .track_fields = true }, struct { layout: ?*gpu.PipelineLayout = null, /// Number of sprites this pipeline will render. - /// Read-only, updated as part of Sprite.update + /// Read-only, updated as part of Sprite.tick num_sprites: u32 = 0, /// Internal pipeline state. @@ -191,19 +190,19 @@ pub fn tick(sprite: *Sprite, core: *mach.Core) !void { // information for all its sprites. const any_sprites_updated = blk: { for (pipeline_children.items) |sprite_id| { - if (!sprite.sprites.is(sprite_id)) continue; - if (sprite.sprites.anyUpdated(sprite_id)) break :blk true; + if (!sprite.objects.is(sprite_id)) continue; + if (sprite.objects.anyUpdated(sprite_id)) break :blk true; } break :blk false; }; - if (any_sprites_updated) updatePipelineSprites(sprite, core, pipeline_id, pipeline_children.items); + if (any_sprites_updated) updatePipelineBuffers(sprite, core, pipeline_id, pipeline_children.items); // Do we actually have any sprites to render? pipeline = sprite.pipelines.getValue(pipeline_id); if (pipeline.num_sprites == 0) continue; // TODO(sprite): need a way to specify order of rendering with multiple pipelines - renderSprites(sprite, core, pipeline_id); + renderPipeline(sprite, core, pipeline_id); } } @@ -237,20 +236,20 @@ fn rebuildPipeline( const transforms = device.createBuffer(&.{ .label = label ++ " transforms", .usage = .{ .storage = true, .copy_dst = true }, - .size = @sizeOf(math.Mat4x4) * sprite_buffer_cap, + .size = @sizeOf(math.Mat4x4) * buffer_cap, .mapped_at_creation = .false, }); const uv_transforms = device.createBuffer(&.{ .label = label ++ " uv_transforms", .usage = .{ .storage = true, .copy_dst = true }, // TODO(d3d12): uv_transform should be a Mat3x3 but our D3D12/HLSL backend cannot handle it. - .size = @sizeOf(math.Mat4x4) * sprite_buffer_cap, + .size = @sizeOf(math.Mat4x4) * buffer_cap, .mapped_at_creation = .false, }); const sizes = device.createBuffer(&.{ .label = label ++ " sizes", .usage = .{ .storage = true, .copy_dst = true }, - .size = @sizeOf(math.Vec2) * sprite_buffer_cap, + .size = @sizeOf(math.Vec2) * buffer_cap, .mapped_at_creation = .false, }); @@ -296,9 +295,9 @@ fn rebuildPipeline( .layout = bind_group_layout, .entries = &.{ gpu.BindGroup.Entry.initBuffer(0, uniforms, 0, @sizeOf(Uniforms), @sizeOf(Uniforms)), - gpu.BindGroup.Entry.initBuffer(1, transforms, 0, @sizeOf(math.Mat4x4) * sprite_buffer_cap, @sizeOf(math.Mat4x4)), - gpu.BindGroup.Entry.initBuffer(2, uv_transforms, 0, @sizeOf(math.Mat3x3) * sprite_buffer_cap, @sizeOf(math.Mat3x3)), - gpu.BindGroup.Entry.initBuffer(3, sizes, 0, @sizeOf(math.Vec2) * sprite_buffer_cap, @sizeOf(math.Vec2)), + gpu.BindGroup.Entry.initBuffer(1, transforms, 0, @sizeOf(math.Mat4x4) * buffer_cap, @sizeOf(math.Mat4x4)), + gpu.BindGroup.Entry.initBuffer(2, uv_transforms, 0, @sizeOf(math.Mat3x3) * buffer_cap, @sizeOf(math.Mat3x3)), + gpu.BindGroup.Entry.initBuffer(3, sizes, 0, @sizeOf(math.Vec2) * buffer_cap, @sizeOf(math.Vec2)), gpu.BindGroup.Entry.initSampler(4, texture_sampler), gpu.BindGroup.Entry.initTextureView(5, texture_view), gpu.BindGroup.Entry.initTextureView(6, texture2_view), @@ -367,7 +366,7 @@ fn rebuildPipeline( pipeline.num_sprites = 0; } -fn updatePipelineSprites( +fn updatePipelineBuffers( sprite: *Sprite, core: *mach.Core, pipeline_id: mach.ObjectID, @@ -377,14 +376,14 @@ fn updatePipelineSprites( const window = core.windows.getValue(pipeline.window.?); const device = window.device; - const label = @tagName(mach_module) ++ ".updatePipelineSprites"; + const label = @tagName(mach_module) ++ ".updatePipelineBuffers"; const encoder = device.createCommandEncoder(&.{ .label = label }); defer encoder.release(); var i: u32 = 0; for (pipeline_children) |sprite_id| { - if (!sprite.sprites.is(sprite_id)) continue; - const s = sprite.sprites.getValue(sprite_id); + if (!sprite.objects.is(sprite_id)) continue; + const s = sprite.objects.getValue(sprite_id); cp_transforms[i] = s.transform; @@ -437,7 +436,7 @@ fn updatePipelineSprites( } } -fn renderSprites( +fn renderPipeline( sprite: *Sprite, core: *mach.Core, pipeline_id: mach.ObjectID, @@ -446,7 +445,7 @@ fn renderSprites( const window = core.windows.getValue(pipeline.window.?); const device = window.device; - const label = @tagName(mach_module) ++ ".renderSprites"; + const label = @tagName(mach_module) ++ ".renderPipeline"; const encoder = device.createCommandEncoder(&.{ .label = label }); defer encoder.release();