all: update to global vs. local ECS change

See hexops/mach-ecs@ef06fb6473

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2023-12-16 19:40:26 -07:00
parent 11aed4d16f
commit 8ff30c931f
4 changed files with 649 additions and 642 deletions

View file

@ -12,8 +12,8 @@
}, },
.dependencies = .{ .dependencies = .{
.mach_ecs = .{ .mach_ecs = .{
.url = "https://pkg.machengine.org/mach-ecs/46fbc9175a70a8cc983b88f911aa076b625e0fb2.tar.gz", .url = "https://pkg.machengine.org/mach-ecs/ef06fb647353356eff080ad3ec337e028b492d41.tar.gz",
.hash = "1220acdc7bdb09425ccdd92bb906c4e30e3391c95ab15049c946c98cc6643a7ad250", .hash = "1220014851adb37d191430ac371cc467e0e0eb633b84b856c7a37e41a3149dea7ce8",
}, },
.mach_core = .{ .mach_core = .{
.url = "https://pkg.machengine.org/mach-core/cce02fb96ca787378289c5855b381f0ca9f3e090.tar.gz", .url = "https://pkg.machengine.org/mach-core/cce02fb96ca787378289c5855b381f0ca9f3e090.tar.gz",

View file

@ -16,80 +16,83 @@ pub const Engine = struct {
pub const name = .engine; pub const name = .engine;
pub fn engineInit(world: *World) !void { pub const local = struct {
core.allocator = allocator; pub fn init(world: *World) !void {
try core.init(.{}); core.allocator = allocator;
const state = &world.mod.engine.state; try core.init(.{});
state.device = core.device; const state = &world.mod.engine.state;
state.queue = core.device.getQueue(); state.device = core.device;
state.exit = false; state.queue = core.device.getQueue();
state.encoder = state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{ state.exit = false;
.label = "engine.state.encoder", state.encoder = state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{
}); .label = "engine.state.encoder",
});
try world.send(.init, .{}); try world.send(null, .init, .{});
} }
pub fn engineDeinit( pub fn deinit(
world: *World, world: *World,
engine: *World.Mod(.engine), engine: *World.Mod(.engine),
) !void { ) !void {
// TODO: this triggers a device loss error, which we should handle correctly // TODO: this triggers a device loss error, which we should handle correctly
// engine.state.device.release(); // engine.state.device.release();
engine.state.queue.release(); engine.state.queue.release();
try world.send(.deinit, .{}); try world.send(null, .deinit, .{});
core.deinit(); core.deinit();
world.deinit(); world.deinit();
_ = gpa.deinit(); _ = gpa.deinit();
} }
pub fn engineExit(world: *World) !void { // Engine module's exit handler
try world.send(.exit, .{}); pub fn exit(world: *World) !void {
world.mod.engine.state.exit = true; try world.send(null, .exit, .{});
} world.mod.engine.state.exit = true;
}
pub fn engineBeginPass( pub fn beginPass(
engine: *World.Mod(.engine), engine: *World.Mod(.engine),
clear_color: gpu.Color, clear_color: gpu.Color,
) !void { ) !void {
const back_buffer_view = core.swap_chain.getCurrentTextureView().?; const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
defer back_buffer_view.release(); defer back_buffer_view.release();
// TODO: expose options // TODO: expose options
const color_attachment = gpu.RenderPassColorAttachment{ const color_attachment = gpu.RenderPassColorAttachment{
.view = back_buffer_view, .view = back_buffer_view,
.clear_value = clear_color, .clear_value = clear_color,
.load_op = .clear, .load_op = .clear,
.store_op = .store, .store_op = .store,
}; };
const pass_info = gpu.RenderPassDescriptor.init(.{ const pass_info = gpu.RenderPassDescriptor.init(.{
.color_attachments = &.{color_attachment}, .color_attachments = &.{color_attachment},
}); });
engine.state.pass = engine.state.encoder.beginRenderPass(&pass_info); engine.state.pass = engine.state.encoder.beginRenderPass(&pass_info);
} }
pub fn engineEndPass( pub fn endPass(
engine: *World.Mod(.engine), engine: *World.Mod(.engine),
) !void { ) !void {
// End this pass // End this pass
engine.state.pass.end(); engine.state.pass.end();
engine.state.pass.release(); engine.state.pass.release();
var command = engine.state.encoder.finish(null); var command = engine.state.encoder.finish(null);
defer command.release(); defer command.release();
engine.state.encoder.release(); engine.state.encoder.release();
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command}); engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
// Prepare for next pass // Prepare for next pass
engine.state.encoder = engine.state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{ engine.state.encoder = engine.state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{
.label = "engine.state.encoder", .label = "engine.state.encoder",
}); });
} }
pub fn enginePresent() !void { pub fn present() !void {
core.swap_chain.present(); core.swap_chain.present();
} }
};
}; };
pub const App = struct { pub const App = struct {
@ -97,15 +100,15 @@ pub const App = struct {
pub fn init(app: *@This()) !void { pub fn init(app: *@This()) !void {
app.* = .{ .world = try World.init(allocator) }; app.* = .{ .world = try World.init(allocator) };
try app.world.send(.engineInit, .{}); try app.world.send(.engine, .init, .{});
} }
pub fn deinit(app: *@This()) void { pub fn deinit(app: *@This()) void {
try app.world.send(.engineDeinit, .{}); try app.world.send(.engine, .deinit, .{});
} }
pub fn update(app: *@This()) !bool { pub fn update(app: *@This()) !bool {
try app.world.send(.tick, .{}); try app.world.send(null, .tick, .{});
return app.world.mod.engine.state.exit; return app.world.mod.engine.state.exit;
} }
}; };

View file

@ -120,249 +120,251 @@ pub const PipelineOptions = struct {
pipeline_layout: ?*gpu.PipelineLayout = null, pipeline_layout: ?*gpu.PipelineLayout = null,
}; };
pub fn machGfxSpriteInit(
sprite_mod: *mach.Mod(.mach_gfx_sprite),
) !void {
sprite_mod.state = .{
// TODO: struct default value initializers don't work
.pipelines = .{},
};
}
pub fn machGfxSpriteInitPipeline(
engine: *mach.Mod(.engine),
sprite_mod: *mach.Mod(.mach_gfx_sprite),
opt: PipelineOptions,
) !void {
const device = engine.state.device;
const pipeline = try sprite_mod.state.pipelines.getOrPut(engine.allocator, opt.pipeline);
if (pipeline.found_existing) {
pipeline.value_ptr.*.deinit();
}
// Storage buffers
const sprite_buffer_cap = 1024 * 512; // TODO: allow user to specify preallocation
const transforms = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Mat4x4) * sprite_buffer_cap,
.mapped_at_creation = .false,
});
const uv_transforms = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Mat3x3) * sprite_buffer_cap,
.mapped_at_creation = .false,
});
const sizes = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Vec2) * sprite_buffer_cap,
.mapped_at_creation = .false,
});
const texture_sampler = opt.texture_sampler orelse device.createSampler(&.{
.mag_filter = .nearest,
.min_filter = .nearest,
});
const uniforms = device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(Uniforms),
.mapped_at_creation = .false,
});
const bind_group_layout = opt.bind_group_layout orelse device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.sampler(4, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(5, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(6, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(7, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(8, .{ .fragment = true }, .float, .dimension_2d, false),
},
}),
);
defer bind_group_layout.release();
const texture_view = opt.texture.createView(&gpu.TextureView.Descriptor{});
const texture2_view = if (opt.texture2) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
const texture3_view = if (opt.texture3) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
const texture4_view = if (opt.texture4) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
defer texture_view.release();
defer texture2_view.release();
defer texture3_view.release();
defer texture4_view.release();
const bind_group = opt.bind_group orelse device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniforms, 0, @sizeOf(Uniforms)),
gpu.BindGroup.Entry.buffer(1, transforms, 0, @sizeOf(Mat4x4) * sprite_buffer_cap),
gpu.BindGroup.Entry.buffer(2, uv_transforms, 0, @sizeOf(Mat3x3) * sprite_buffer_cap),
gpu.BindGroup.Entry.buffer(3, sizes, 0, @sizeOf(Vec2) * sprite_buffer_cap),
gpu.BindGroup.Entry.sampler(4, texture_sampler),
gpu.BindGroup.Entry.textureView(5, texture_view),
gpu.BindGroup.Entry.textureView(6, texture2_view),
gpu.BindGroup.Entry.textureView(7, texture3_view),
gpu.BindGroup.Entry.textureView(8, texture4_view),
},
}),
);
const blend_state = opt.blend_state orelse gpu.BlendState{
.color = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
const shader_module = opt.shader orelse device.createShaderModuleWGSL("sprite.wgsl", @embedFile("sprite.wgsl"));
defer shader_module.release();
const color_target = opt.color_target_state orelse gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend_state,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = opt.fragment_state orelse gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "fragMain",
.targets = &.{color_target},
});
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
const pipeline_layout = opt.pipeline_layout orelse device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
defer pipeline_layout.release();
const render = device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState{
.module = shader_module,
.entry_point = "vertMain",
},
});
pipeline.value_ptr.* = Pipeline{
.render = render,
.texture_sampler = texture_sampler,
.texture = opt.texture,
.texture2 = opt.texture2,
.texture3 = opt.texture3,
.texture4 = opt.texture4,
.bind_group = bind_group,
.uniforms = uniforms,
.num_sprites = 0,
.transforms = transforms,
.uv_transforms = uv_transforms,
.sizes = sizes,
};
pipeline.value_ptr.reference();
}
pub fn deinit(sprite_mod: *mach.Mod(.mach_gfx_sprite)) !void { pub fn deinit(sprite_mod: *mach.Mod(.mach_gfx_sprite)) !void {
for (sprite_mod.state.pipelines.entries.items(.value)) |*pipeline| pipeline.deinit(); for (sprite_mod.state.pipelines.entries.items(.value)) |*pipeline| pipeline.deinit();
sprite_mod.state.pipelines.deinit(sprite_mod.allocator); sprite_mod.state.pipelines.deinit(sprite_mod.allocator);
} }
pub fn machGfxSpriteUpdated( pub const local = struct {
engine: *mach.Mod(.engine), pub fn init(
sprite_mod: *mach.Mod(.mach_gfx_sprite), sprite_mod: *mach.Mod(.mach_gfx_sprite),
pipeline_id: u32, ) !void {
) !void { sprite_mod.state = .{
const pipeline = sprite_mod.state.pipelines.getPtr(pipeline_id).?; // TODO: struct default value initializers don't work
const device = engine.state.device; .pipelines = .{},
};
// TODO: make sure these entities only belong to the given pipeline
// we need a better tagging mechanism
var archetypes_iter = engine.entities.query(.{ .all = &.{
.{ .mach_gfx_sprite = &.{
.uv_transform,
.transform,
.size,
.pipeline,
} },
} });
const encoder = device.createCommandEncoder(null);
defer encoder.release();
pipeline.num_sprites = 0;
var transforms_offset: usize = 0;
var uv_transforms_offset: usize = 0;
var sizes_offset: usize = 0;
while (archetypes_iter.next()) |archetype| {
var transforms = archetype.slice(.mach_gfx_sprite, .transform);
var uv_transforms = archetype.slice(.mach_gfx_sprite, .uv_transform);
var sizes = archetype.slice(.mach_gfx_sprite, .size);
// TODO: confirm the lifetime of these slices is OK for writeBuffer, how long do they need
// to live?
encoder.writeBuffer(pipeline.transforms, transforms_offset, transforms);
encoder.writeBuffer(pipeline.uv_transforms, uv_transforms_offset, uv_transforms);
encoder.writeBuffer(pipeline.sizes, sizes_offset, sizes);
transforms_offset += transforms.len;
uv_transforms_offset += uv_transforms.len;
sizes_offset += sizes.len;
pipeline.num_sprites += @intCast(transforms.len);
} }
var command = encoder.finish(null); pub fn initPipeline(
defer command.release(); engine: *mach.Mod(.engine),
sprite_mod: *mach.Mod(.mach_gfx_sprite),
opt: PipelineOptions,
) !void {
const device = engine.state.device;
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command}); const pipeline = try sprite_mod.state.pipelines.getOrPut(engine.allocator, opt.pipeline);
} if (pipeline.found_existing) {
pipeline.value_ptr.*.deinit();
}
pub fn machGfxSpritePreRender( // Storage buffers
engine: *mach.Mod(.engine), const sprite_buffer_cap = 1024 * 512; // TODO: allow user to specify preallocation
sprite_mod: *mach.Mod(.mach_gfx_sprite), const transforms = device.createBuffer(&.{
pipeline_id: u32, .usage = .{ .storage = true, .copy_dst = true },
) !void { .size = @sizeOf(Mat4x4) * sprite_buffer_cap,
const pipeline = sprite_mod.state.pipelines.get(pipeline_id).?; .mapped_at_creation = .false,
});
const uv_transforms = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Mat3x3) * sprite_buffer_cap,
.mapped_at_creation = .false,
});
const sizes = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Vec2) * sprite_buffer_cap,
.mapped_at_creation = .false,
});
// Update uniform buffer const texture_sampler = opt.texture_sampler orelse device.createSampler(&.{
const ortho = Mat4x4.ortho( .mag_filter = .nearest,
-@as(f32, @floatFromInt(core.size().width)) / 2, .min_filter = .nearest,
@as(f32, @floatFromInt(core.size().width)) / 2, });
-@as(f32, @floatFromInt(core.size().height)) / 2, const uniforms = device.createBuffer(&.{
@as(f32, @floatFromInt(core.size().height)) / 2, .usage = .{ .copy_dst = true, .uniform = true },
-0.1, .size = @sizeOf(Uniforms),
100000, .mapped_at_creation = .false,
); });
const uniforms = Uniforms{ const bind_group_layout = opt.bind_group_layout orelse device.createBindGroupLayout(
.view_projection = ortho, &gpu.BindGroupLayout.Descriptor.init(.{
// TODO: dimensions of other textures, number of textures present .entries = &.{
.texture_size = vec2( gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
@as(f32, @floatFromInt(pipeline.texture.getWidth())), gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .read_only_storage, false, 0),
@as(f32, @floatFromInt(pipeline.texture.getHeight())), gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .read_only_storage, false, 0),
), gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .read_only_storage, false, 0),
}; gpu.BindGroupLayout.Entry.sampler(4, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(5, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(6, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(7, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(8, .{ .fragment = true }, .float, .dimension_2d, false),
},
}),
);
defer bind_group_layout.release();
engine.state.encoder.writeBuffer(pipeline.uniforms, 0, &[_]Uniforms{uniforms}); const texture_view = opt.texture.createView(&gpu.TextureView.Descriptor{});
} const texture2_view = if (opt.texture2) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
const texture3_view = if (opt.texture3) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
const texture4_view = if (opt.texture4) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
defer texture_view.release();
defer texture2_view.release();
defer texture3_view.release();
defer texture4_view.release();
pub fn machGfxSpriteRender( const bind_group = opt.bind_group orelse device.createBindGroup(
engine: *mach.Mod(.engine), &gpu.BindGroup.Descriptor.init(.{
sprite_mod: *mach.Mod(.mach_gfx_sprite), .layout = bind_group_layout,
pipeline_id: u32, .entries = &.{
) !void { gpu.BindGroup.Entry.buffer(0, uniforms, 0, @sizeOf(Uniforms)),
const pipeline = sprite_mod.state.pipelines.get(pipeline_id).?; gpu.BindGroup.Entry.buffer(1, transforms, 0, @sizeOf(Mat4x4) * sprite_buffer_cap),
gpu.BindGroup.Entry.buffer(2, uv_transforms, 0, @sizeOf(Mat3x3) * sprite_buffer_cap),
gpu.BindGroup.Entry.buffer(3, sizes, 0, @sizeOf(Vec2) * sprite_buffer_cap),
gpu.BindGroup.Entry.sampler(4, texture_sampler),
gpu.BindGroup.Entry.textureView(5, texture_view),
gpu.BindGroup.Entry.textureView(6, texture2_view),
gpu.BindGroup.Entry.textureView(7, texture3_view),
gpu.BindGroup.Entry.textureView(8, texture4_view),
},
}),
);
// Draw the sprite batch const blend_state = opt.blend_state orelse gpu.BlendState{
const pass = engine.state.pass; .color = .{
const total_vertices = pipeline.num_sprites * 6; .operation = .add,
pass.setPipeline(pipeline.render); .src_factor = .src_alpha,
// TODO: remove dynamic offsets? .dst_factor = .one_minus_src_alpha,
pass.setBindGroup(0, pipeline.bind_group, &.{}); },
pass.draw(total_vertices, 1, 0, 0); .alpha = .{
} .operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
const shader_module = opt.shader orelse device.createShaderModuleWGSL("sprite.wgsl", @embedFile("sprite.wgsl"));
defer shader_module.release();
const color_target = opt.color_target_state orelse gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend_state,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = opt.fragment_state orelse gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "fragMain",
.targets = &.{color_target},
});
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
const pipeline_layout = opt.pipeline_layout orelse device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
defer pipeline_layout.release();
const render_pipeline = device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState{
.module = shader_module,
.entry_point = "vertMain",
},
});
pipeline.value_ptr.* = Pipeline{
.render = render_pipeline,
.texture_sampler = texture_sampler,
.texture = opt.texture,
.texture2 = opt.texture2,
.texture3 = opt.texture3,
.texture4 = opt.texture4,
.bind_group = bind_group,
.uniforms = uniforms,
.num_sprites = 0,
.transforms = transforms,
.uv_transforms = uv_transforms,
.sizes = sizes,
};
pipeline.value_ptr.reference();
}
pub fn updated(
engine: *mach.Mod(.engine),
sprite_mod: *mach.Mod(.mach_gfx_sprite),
pipeline_id: u32,
) !void {
const pipeline = sprite_mod.state.pipelines.getPtr(pipeline_id).?;
const device = engine.state.device;
// TODO: make sure these entities only belong to the given pipeline
// we need a better tagging mechanism
var archetypes_iter = engine.entities.query(.{ .all = &.{
.{ .mach_gfx_sprite = &.{
.uv_transform,
.transform,
.size,
.pipeline,
} },
} });
const encoder = device.createCommandEncoder(null);
defer encoder.release();
pipeline.num_sprites = 0;
var transforms_offset: usize = 0;
var uv_transforms_offset: usize = 0;
var sizes_offset: usize = 0;
while (archetypes_iter.next()) |archetype| {
var transforms = archetype.slice(.mach_gfx_sprite, .transform);
var uv_transforms = archetype.slice(.mach_gfx_sprite, .uv_transform);
var sizes = archetype.slice(.mach_gfx_sprite, .size);
// TODO: confirm the lifetime of these slices is OK for writeBuffer, how long do they need
// to live?
encoder.writeBuffer(pipeline.transforms, transforms_offset, transforms);
encoder.writeBuffer(pipeline.uv_transforms, uv_transforms_offset, uv_transforms);
encoder.writeBuffer(pipeline.sizes, sizes_offset, sizes);
transforms_offset += transforms.len;
uv_transforms_offset += uv_transforms.len;
sizes_offset += sizes.len;
pipeline.num_sprites += @intCast(transforms.len);
}
var command = encoder.finish(null);
defer command.release();
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
}
pub fn preRender(
engine: *mach.Mod(.engine),
sprite_mod: *mach.Mod(.mach_gfx_sprite),
pipeline_id: u32,
) !void {
const pipeline = sprite_mod.state.pipelines.get(pipeline_id).?;
// Update uniform buffer
const ortho = Mat4x4.ortho(
-@as(f32, @floatFromInt(core.size().width)) / 2,
@as(f32, @floatFromInt(core.size().width)) / 2,
-@as(f32, @floatFromInt(core.size().height)) / 2,
@as(f32, @floatFromInt(core.size().height)) / 2,
-0.1,
100000,
);
const uniforms = Uniforms{
.view_projection = ortho,
// TODO: dimensions of other textures, number of textures present
.texture_size = vec2(
@as(f32, @floatFromInt(pipeline.texture.getWidth())),
@as(f32, @floatFromInt(pipeline.texture.getHeight())),
),
};
engine.state.encoder.writeBuffer(pipeline.uniforms, 0, &[_]Uniforms{uniforms});
}
pub fn render(
engine: *mach.Mod(.engine),
sprite_mod: *mach.Mod(.mach_gfx_sprite),
pipeline_id: u32,
) !void {
const pipeline = sprite_mod.state.pipelines.get(pipeline_id).?;
// Draw the sprite batch
const pass = engine.state.pass;
const total_vertices = pipeline.num_sprites * 6;
pass.setPipeline(pipeline.render);
// TODO: remove dynamic offsets?
pass.setBindGroup(0, pipeline.bind_group, &.{});
pass.draw(total_vertices, 1, 0, 0);
}
};

View file

@ -170,374 +170,376 @@ pub const PipelineOptions = struct {
pipeline_layout: ?*gpu.PipelineLayout = null, pipeline_layout: ?*gpu.PipelineLayout = null,
}; };
pub fn machGfxTextInit(
text_mod: *mach.Mod(.mach_gfx_text),
) !void {
text_mod.state = .{
// TODO: struct default value initializers don't work
.pipelines = .{},
};
}
pub fn machGfxTextInitPipeline(
engine: *mach.Mod(.engine),
text_mod: *mach.Mod(.mach_gfx_text),
opt: PipelineOptions,
) !void {
const device = engine.state.device;
const pipeline = try text_mod.state.pipelines.getOrPut(engine.allocator, opt.pipeline);
if (pipeline.found_existing) {
pipeline.value_ptr.*.deinit(engine.allocator);
}
// Prepare texture for the font atlas.
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
const texture = device.createTexture(&.{
.size = img_size,
.format = .rgba8_unorm,
.usage = .{
.texture_binding = true,
.copy_dst = true,
.render_attachment = true,
},
});
const texture_atlas = try gfx.Atlas.init(
engine.allocator,
img_size.width,
.rgba,
);
// Storage buffers
const buffer_cap = 1024 * 128; // TODO: allow user to specify preallocation
const glyph_buffer_cap = 1024 * 512; // TODO: allow user to specify preallocation
const transforms = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Mat4x4) * buffer_cap,
.mapped_at_creation = .false,
});
const colors = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Vec4) * buffer_cap,
.mapped_at_creation = .false,
});
const glyphs = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Glyph) * glyph_buffer_cap,
.mapped_at_creation = .false,
});
const texture_sampler = opt.texture_sampler orelse device.createSampler(&.{
.mag_filter = .nearest,
.min_filter = .nearest,
});
const uniforms = device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(Uniforms),
.mapped_at_creation = .false,
});
const bind_group_layout = opt.bind_group_layout orelse device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.sampler(4, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(5, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(6, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(7, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(8, .{ .fragment = true }, .float, .dimension_2d, false),
},
}),
);
defer bind_group_layout.release();
const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
const texture2_view = if (opt.texture2) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
const texture3_view = if (opt.texture3) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
const texture4_view = if (opt.texture4) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
defer texture_view.release();
defer texture2_view.release();
defer texture3_view.release();
defer texture4_view.release();
const bind_group = opt.bind_group orelse device.createBindGroup(
&gpu.BindGroup.Descriptor.init(.{
.layout = bind_group_layout,
.entries = &.{
gpu.BindGroup.Entry.buffer(0, uniforms, 0, @sizeOf(Uniforms)),
gpu.BindGroup.Entry.buffer(1, transforms, 0, @sizeOf(Mat4x4) * buffer_cap),
gpu.BindGroup.Entry.buffer(2, colors, 0, @sizeOf(Vec4) * buffer_cap),
gpu.BindGroup.Entry.buffer(3, glyphs, 0, @sizeOf(Glyph) * glyph_buffer_cap),
gpu.BindGroup.Entry.sampler(4, texture_sampler),
gpu.BindGroup.Entry.textureView(5, texture_view),
gpu.BindGroup.Entry.textureView(6, texture2_view),
gpu.BindGroup.Entry.textureView(7, texture3_view),
gpu.BindGroup.Entry.textureView(8, texture4_view),
},
}),
);
const blend_state = opt.blend_state orelse gpu.BlendState{
.color = .{
.operation = .add,
.src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
const shader_module = opt.shader orelse device.createShaderModuleWGSL("text.wgsl", @embedFile("text.wgsl"));
defer shader_module.release();
const color_target = opt.color_target_state orelse gpu.ColorTargetState{
.format = core.descriptor.format,
.blend = &blend_state,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = opt.fragment_state orelse gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "fragMain",
.targets = &.{color_target},
});
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
const pipeline_layout = opt.pipeline_layout orelse device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
.bind_group_layouts = &bind_group_layouts,
}));
defer pipeline_layout.release();
const render = device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
.fragment = &fragment,
.layout = pipeline_layout,
.vertex = gpu.VertexState{
.module = shader_module,
.entry_point = "vertMain",
},
});
pipeline.value_ptr.* = Pipeline{
.render = render,
.texture_sampler = texture_sampler,
.texture = texture,
.texture_atlas = texture_atlas,
.texture2 = opt.texture2,
.texture3 = opt.texture3,
.texture4 = opt.texture4,
.bind_group = bind_group,
.uniforms = uniforms,
.num_texts = 0,
.num_glyphs = 0,
.transforms = transforms,
.colors = colors,
.glyphs = glyphs,
};
pipeline.value_ptr.reference();
}
pub fn deinit(text_mod: *mach.Mod(.mach_gfx_text)) !void { pub fn deinit(text_mod: *mach.Mod(.mach_gfx_text)) !void {
for (text_mod.state.pipelines.entries.items(.value)) |*pipeline| pipeline.deinit(text_mod.allocator); for (text_mod.state.pipelines.entries.items(.value)) |*pipeline| pipeline.deinit(text_mod.allocator);
text_mod.state.pipelines.deinit(text_mod.allocator); text_mod.state.pipelines.deinit(text_mod.allocator);
} }
pub fn machGfxTextUpdated( pub const local = struct {
engine: *mach.Mod(.engine), pub fn init(
text_mod: *mach.Mod(.mach_gfx_text), text_mod: *mach.Mod(.mach_gfx_text),
pipeline_id: u32, ) !void {
) !void { text_mod.state = .{
const pipeline = text_mod.state.pipelines.getPtr(pipeline_id).?; // TODO: struct default value initializers don't work
const device = engine.state.device; .pipelines = .{},
};
}
// TODO: make sure these entities only belong to the given pipeline pub fn initPipeline(
// we need a better tagging mechanism engine: *mach.Mod(.engine),
var archetypes_iter = engine.entities.query(.{ .all = &.{ text_mod: *mach.Mod(.mach_gfx_text),
.{ .mach_gfx_text = &.{ opt: PipelineOptions,
.pipeline, ) !void {
.transform, const device = engine.state.device;
.text,
} },
} });
const encoder = device.createCommandEncoder(null); const pipeline = try text_mod.state.pipelines.getOrPut(engine.allocator, opt.pipeline);
defer encoder.release(); if (pipeline.found_existing) {
pipeline.value_ptr.*.deinit(engine.allocator);
}
pipeline.num_texts = 0; // Prepare texture for the font atlas.
pipeline.num_glyphs = 0; const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
var glyphs = std.ArrayListUnmanaged(Glyph){}; const texture = device.createTexture(&.{
var transforms_offset: usize = 0; .size = img_size,
// var colors_offset: usize = 0; .format = .rgba8_unorm,
var texture_update = false; .usage = .{
while (archetypes_iter.next()) |archetype| { .texture_binding = true,
var transforms = archetype.slice(.mach_gfx_text, .transform); .copy_dst = true,
// var colors = archetype.slice(.mach_gfx_text, .color); .render_attachment = true,
},
});
const texture_atlas = try gfx.Atlas.init(
engine.allocator,
img_size.width,
.rgba,
);
// TODO: confirm the lifetime of these slices is OK for writeBuffer, how long do they need // Storage buffers
// to live? const buffer_cap = 1024 * 128; // TODO: allow user to specify preallocation
encoder.writeBuffer(pipeline.transforms, transforms_offset, transforms); const glyph_buffer_cap = 1024 * 512; // TODO: allow user to specify preallocation
// encoder.writeBuffer(pipeline.colors, colors_offset, colors); const transforms = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Mat4x4) * buffer_cap,
.mapped_at_creation = .false,
});
const colors = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Vec4) * buffer_cap,
.mapped_at_creation = .false,
});
const glyphs = device.createBuffer(&.{
.usage = .{ .storage = true, .copy_dst = true },
.size = @sizeOf(Glyph) * glyph_buffer_cap,
.mapped_at_creation = .false,
});
transforms_offset += transforms.len; const texture_sampler = opt.texture_sampler orelse device.createSampler(&.{
// colors_offset += colors.len; .mag_filter = .nearest,
pipeline.num_texts += @intCast(transforms.len); .min_filter = .nearest,
});
const uniforms = device.createBuffer(&.{
.usage = .{ .copy_dst = true, .uniform = true },
.size = @sizeOf(Uniforms),
.mapped_at_creation = .false,
});
const bind_group_layout = opt.bind_group_layout orelse device.createBindGroupLayout(
&gpu.BindGroupLayout.Descriptor.init(.{
.entries = &.{
gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .read_only_storage, false, 0),
gpu.BindGroupLayout.Entry.sampler(4, .{ .fragment = true }, .filtering),
gpu.BindGroupLayout.Entry.texture(5, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(6, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(7, .{ .fragment = true }, .float, .dimension_2d, false),
gpu.BindGroupLayout.Entry.texture(8, .{ .fragment = true }, .float, .dimension_2d, false),
},
}),
);
defer bind_group_layout.release();
// Render texts const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
// TODO: this is very expensive and shouldn't be done here, should be done only on detected const texture2_view = if (opt.texture2) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
// text change. const texture3_view = if (opt.texture3) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
const px_density = 2.0; const texture4_view = if (opt.texture4) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
// var font_names = archetype.slice(.mach_gfx_text, .font_name); defer texture_view.release();
// var font_sizes = archetype.slice(.mach_gfx_text, .font_size); defer texture2_view.release();
var texts = archetype.slice(.mach_gfx_text, .text); defer texture3_view.release();
for (texts) |text| { defer texture4_view.release();
var origin_x: f32 = 0.0;
var origin_y: f32 = 0.0;
for (text) |segment| { const bind_group = opt.bind_group orelse device.createBindGroup(
// Load a font &gpu.BindGroup.Descriptor.init(.{
// TODO: resolve font by name, not hard-code .layout = bind_group_layout,
const font_bytes = @import("font-assets").fira_sans_regular_ttf; .entries = &.{
var font = try gfx.Font.initBytes(font_bytes); gpu.BindGroup.Entry.buffer(0, uniforms, 0, @sizeOf(Uniforms)),
defer font.deinit(engine.allocator); gpu.BindGroup.Entry.buffer(1, transforms, 0, @sizeOf(Mat4x4) * buffer_cap),
gpu.BindGroup.Entry.buffer(2, colors, 0, @sizeOf(Vec4) * buffer_cap),
gpu.BindGroup.Entry.buffer(3, glyphs, 0, @sizeOf(Glyph) * glyph_buffer_cap),
gpu.BindGroup.Entry.sampler(4, texture_sampler),
gpu.BindGroup.Entry.textureView(5, texture_view),
gpu.BindGroup.Entry.textureView(6, texture2_view),
gpu.BindGroup.Entry.textureView(7, texture3_view),
gpu.BindGroup.Entry.textureView(8, texture4_view),
},
}),
);
// Create a text shaper const blend_state = opt.blend_state orelse gpu.BlendState{
var run = try gfx.TextRun.init(); .color = .{
run.font_size_px = segment.style.font_size; .operation = .add,
run.px_density = 2; // TODO .src_factor = .src_alpha,
.dst_factor = .one_minus_src_alpha,
},
.alpha = .{
.operation = .add,
.src_factor = .one,
.dst_factor = .zero,
},
};
defer run.deinit(); const shader_module = opt.shader orelse device.createShaderModuleWGSL("text.wgsl", @embedFile("text.wgsl"));
defer shader_module.release();
run.addText(segment.string); const color_target = opt.color_target_state orelse gpu.ColorTargetState{
try font.shape(&run); .format = core.descriptor.format,
.blend = &blend_state,
.write_mask = gpu.ColorWriteMaskFlags.all,
};
const fragment = opt.fragment_state orelse gpu.FragmentState.init(.{
.module = shader_module,
.entry_point = "fragMain",
.targets = &.{color_target},
});
while (run.next()) |glyph| { const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
const codepoint = segment.string[glyph.cluster]; const pipeline_layout = opt.pipeline_layout orelse device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
// TODO: use flags(?) to detect newline, or at least something more reliable? .bind_group_layouts = &bind_group_layouts,
if (codepoint != '\n') { }));
var region = try pipeline.regions.getOrPut(engine.allocator, .{ defer pipeline_layout.release();
.index = glyph.glyph_index, const render_pipeline = device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
.size = @bitCast(segment.style.font_size), .fragment = &fragment,
}); .layout = pipeline_layout,
if (!region.found_existing) { .vertex = gpu.VertexState{
const rendered_glyph = try font.render(engine.allocator, glyph.glyph_index, .{ .module = shader_module,
.font_size_px = run.font_size_px, .entry_point = "vertMain",
},
});
pipeline.value_ptr.* = Pipeline{
.render = render_pipeline,
.texture_sampler = texture_sampler,
.texture = texture,
.texture_atlas = texture_atlas,
.texture2 = opt.texture2,
.texture3 = opt.texture3,
.texture4 = opt.texture4,
.bind_group = bind_group,
.uniforms = uniforms,
.num_texts = 0,
.num_glyphs = 0,
.transforms = transforms,
.colors = colors,
.glyphs = glyphs,
};
pipeline.value_ptr.reference();
}
pub fn updated(
engine: *mach.Mod(.engine),
text_mod: *mach.Mod(.mach_gfx_text),
pipeline_id: u32,
) !void {
const pipeline = text_mod.state.pipelines.getPtr(pipeline_id).?;
const device = engine.state.device;
// TODO: make sure these entities only belong to the given pipeline
// we need a better tagging mechanism
var archetypes_iter = engine.entities.query(.{ .all = &.{
.{ .mach_gfx_text = &.{
.pipeline,
.transform,
.text,
} },
} });
const encoder = device.createCommandEncoder(null);
defer encoder.release();
pipeline.num_texts = 0;
pipeline.num_glyphs = 0;
var glyphs = std.ArrayListUnmanaged(Glyph){};
var transforms_offset: usize = 0;
// var colors_offset: usize = 0;
var texture_update = false;
while (archetypes_iter.next()) |archetype| {
var transforms = archetype.slice(.mach_gfx_text, .transform);
// var colors = archetype.slice(.mach_gfx_text, .color);
// TODO: confirm the lifetime of these slices is OK for writeBuffer, how long do they need
// to live?
encoder.writeBuffer(pipeline.transforms, transforms_offset, transforms);
// encoder.writeBuffer(pipeline.colors, colors_offset, colors);
transforms_offset += transforms.len;
// colors_offset += colors.len;
pipeline.num_texts += @intCast(transforms.len);
// Render texts
// TODO: this is very expensive and shouldn't be done here, should be done only on detected
// text change.
const px_density = 2.0;
// var font_names = archetype.slice(.mach_gfx_text, .font_name);
// var font_sizes = archetype.slice(.mach_gfx_text, .font_size);
var texts = archetype.slice(.mach_gfx_text, .text);
for (texts) |text| {
var origin_x: f32 = 0.0;
var origin_y: f32 = 0.0;
for (text) |segment| {
// Load a font
// TODO: resolve font by name, not hard-code
const font_bytes = @import("font-assets").fira_sans_regular_ttf;
var font = try gfx.Font.initBytes(font_bytes);
defer font.deinit(engine.allocator);
// Create a text shaper
var run = try gfx.TextRun.init();
run.font_size_px = segment.style.font_size;
run.px_density = 2; // TODO
defer run.deinit();
run.addText(segment.string);
try font.shape(&run);
while (run.next()) |glyph| {
const codepoint = segment.string[glyph.cluster];
// TODO: use flags(?) to detect newline, or at least something more reliable?
if (codepoint != '\n') {
var region = try pipeline.regions.getOrPut(engine.allocator, .{
.index = glyph.glyph_index,
.size = @bitCast(segment.style.font_size),
}); });
if (rendered_glyph.bitmap) |bitmap| { if (!region.found_existing) {
var glyph_atlas_region = try pipeline.texture_atlas.reserve(engine.allocator, rendered_glyph.width, rendered_glyph.height); const rendered_glyph = try font.render(engine.allocator, glyph.glyph_index, .{
pipeline.texture_atlas.set(glyph_atlas_region, @as([*]const u8, @ptrCast(bitmap.ptr))[0 .. bitmap.len * 4]); .font_size_px = run.font_size_px,
texture_update = true; });
if (rendered_glyph.bitmap) |bitmap| {
var glyph_atlas_region = try pipeline.texture_atlas.reserve(engine.allocator, rendered_glyph.width, rendered_glyph.height);
pipeline.texture_atlas.set(glyph_atlas_region, @as([*]const u8, @ptrCast(bitmap.ptr))[0 .. bitmap.len * 4]);
texture_update = true;
// Exclude the 1px blank space margin when describing the region of the texture // Exclude the 1px blank space margin when describing the region of the texture
// that actually represents the glyph. // that actually represents the glyph.
const margin = 1; const margin = 1;
glyph_atlas_region.x += margin; glyph_atlas_region.x += margin;
glyph_atlas_region.y += margin; glyph_atlas_region.y += margin;
glyph_atlas_region.width -= margin * 2; glyph_atlas_region.width -= margin * 2;
glyph_atlas_region.height -= margin * 2; glyph_atlas_region.height -= margin * 2;
region.value_ptr.* = glyph_atlas_region; region.value_ptr.* = glyph_atlas_region;
} else { } else {
// whitespace // whitespace
region.value_ptr.* = gfx.Atlas.Region{ region.value_ptr.* = gfx.Atlas.Region{
.width = 0, .width = 0,
.height = 0, .height = 0,
.x = 0, .x = 0,
.y = 0, .y = 0,
}; };
}
} }
const r = region.value_ptr.*;
const size = vec2(@floatFromInt(r.width), @floatFromInt(r.height));
try glyphs.append(engine.allocator, .{
.pos = vec2(
origin_x + glyph.offset.x(),
origin_y - (size.y() - glyph.offset.y()),
).divScalar(px_density),
.size = size.divScalar(px_density),
.text_index = 0,
.uv_pos = vec2(@floatFromInt(r.x), @floatFromInt(r.y)),
});
pipeline.num_glyphs += 1;
} }
const r = region.value_ptr.*; if (codepoint == '\n') {
const size = vec2(@floatFromInt(r.width), @floatFromInt(r.height)); origin_x = 0;
try glyphs.append(engine.allocator, .{ origin_y -= segment.style.font_size;
.pos = vec2( } else {
origin_x + glyph.offset.x(), origin_x += glyph.advance.x();
origin_y - (size.y() - glyph.offset.y()), }
).divScalar(px_density),
.size = size.divScalar(px_density),
.text_index = 0,
.uv_pos = vec2(@floatFromInt(r.x), @floatFromInt(r.y)),
});
pipeline.num_glyphs += 1;
}
if (codepoint == '\n') {
origin_x = 0;
origin_y -= segment.style.font_size;
} else {
origin_x += glyph.advance.x();
} }
} }
} }
} }
// TODO: could writeBuffer check for zero?
if (glyphs.items.len > 0) encoder.writeBuffer(pipeline.glyphs, 0, glyphs.items);
defer glyphs.deinit(engine.allocator);
if (texture_update) {
// rgba32_pixels
// TODO: use proper texture dimensions here
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
const data_layout = gpu.Texture.DataLayout{
.bytes_per_row = @as(u32, @intCast(img_size.width * 4)),
.rows_per_image = @as(u32, @intCast(img_size.height)),
};
engine.state.queue.writeTexture(
&.{ .texture = pipeline.texture },
&data_layout,
&img_size,
pipeline.texture_atlas.data,
);
}
var command = encoder.finish(null);
defer command.release();
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
} }
// TODO: could writeBuffer check for zero? pub fn preRender(
if (glyphs.items.len > 0) encoder.writeBuffer(pipeline.glyphs, 0, glyphs.items); engine: *mach.Mod(.engine),
defer glyphs.deinit(engine.allocator); text_mod: *mach.Mod(.mach_gfx_text),
if (texture_update) { pipeline_id: u32,
// rgba32_pixels ) !void {
// TODO: use proper texture dimensions here const pipeline = text_mod.state.pipelines.get(pipeline_id).?;
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
const data_layout = gpu.Texture.DataLayout{ // Update uniform buffer
.bytes_per_row = @as(u32, @intCast(img_size.width * 4)), const ortho = Mat4x4.ortho(
.rows_per_image = @as(u32, @intCast(img_size.height)), -@as(f32, @floatFromInt(core.size().width)) / 2,
}; @as(f32, @floatFromInt(core.size().width)) / 2,
engine.state.queue.writeTexture( -@as(f32, @floatFromInt(core.size().height)) / 2,
&.{ .texture = pipeline.texture }, @as(f32, @floatFromInt(core.size().height)) / 2,
&data_layout, -0.1,
&img_size, 100000,
pipeline.texture_atlas.data,
); );
const uniforms = Uniforms{
.view_projection = ortho,
// TODO: dimensions of other textures, number of textures present
.texture_size = vec2(
@as(f32, @floatFromInt(pipeline.texture.getWidth())),
@as(f32, @floatFromInt(pipeline.texture.getHeight())),
),
};
engine.state.encoder.writeBuffer(pipeline.uniforms, 0, &[_]Uniforms{uniforms});
} }
var command = encoder.finish(null); pub fn render(
defer command.release(); engine: *mach.Mod(.engine),
text_mod: *mach.Mod(.mach_gfx_text),
pipeline_id: u32,
) !void {
const pipeline = text_mod.state.pipelines.get(pipeline_id).?;
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command}); // Draw the text batch
} const pass = engine.state.pass;
const total_vertices = pipeline.num_glyphs * 6;
pub fn machGfxTextPreRender( pass.setPipeline(pipeline.render);
engine: *mach.Mod(.engine), // TODO: remove dynamic offsets?
text_mod: *mach.Mod(.mach_gfx_text), pass.setBindGroup(0, pipeline.bind_group, &.{});
pipeline_id: u32, pass.draw(total_vertices, 1, 0, 0);
) !void { }
const pipeline = text_mod.state.pipelines.get(pipeline_id).?; };
// Update uniform buffer
const ortho = Mat4x4.ortho(
-@as(f32, @floatFromInt(core.size().width)) / 2,
@as(f32, @floatFromInt(core.size().width)) / 2,
-@as(f32, @floatFromInt(core.size().height)) / 2,
@as(f32, @floatFromInt(core.size().height)) / 2,
-0.1,
100000,
);
const uniforms = Uniforms{
.view_projection = ortho,
// TODO: dimensions of other textures, number of textures present
.texture_size = vec2(
@as(f32, @floatFromInt(pipeline.texture.getWidth())),
@as(f32, @floatFromInt(pipeline.texture.getHeight())),
),
};
engine.state.encoder.writeBuffer(pipeline.uniforms, 0, &[_]Uniforms{uniforms});
}
pub fn machGfxTextRender(
engine: *mach.Mod(.engine),
text_mod: *mach.Mod(.mach_gfx_text),
pipeline_id: u32,
) !void {
const pipeline = text_mod.state.pipelines.get(pipeline_id).?;
// Draw the text batch
const pass = engine.state.pass;
const total_vertices = pipeline.num_glyphs * 6;
pass.setPipeline(pipeline.render);
// TODO: remove dynamic offsets?
pass.setBindGroup(0, pipeline.bind_group, &.{});
pass.draw(total_vertices, 1, 0, 0);
}