gfx: improve Sprite module pipeline management
* No longer abuse event arguments for pipeline information. * Store pipeline information as entities/components. Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
5714a60108
commit
16a895240d
8 changed files with 486 additions and 377 deletions
|
|
@ -3,7 +3,7 @@ const std = @import("std");
|
|||
const mach = @import("mach");
|
||||
const core = mach.core;
|
||||
const gpu = mach.gpu;
|
||||
const Sprite = mach.gfx.Sprite;
|
||||
const gfx = mach.gfx;
|
||||
const math = mach.math;
|
||||
const vec2 = math.vec2;
|
||||
const vec3 = math.vec3;
|
||||
|
|
@ -24,6 +24,7 @@ frame_count: usize,
|
|||
sprites: usize,
|
||||
rand: std.rand.DefaultPrng,
|
||||
time: f32,
|
||||
pipeline: mach.EntityID,
|
||||
|
||||
const d0 = 0.000001;
|
||||
|
||||
|
|
@ -49,48 +50,28 @@ pub const local_events = .{
|
|||
.after_sprite_init = .{ .handler = afterSpriteInit },
|
||||
};
|
||||
|
||||
pub const Pipeline = enum(u32) {
|
||||
default,
|
||||
text,
|
||||
};
|
||||
|
||||
fn init(
|
||||
sprite_mod: *Sprite.Mod,
|
||||
sprite_mod: *gfx.Sprite.Mod,
|
||||
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
||||
text_mod: *Text.Mod,
|
||||
game: *Mod,
|
||||
) !void {
|
||||
// The Mach .core is where we set window options, etc.
|
||||
core.setTitle("gfx.Sprite example");
|
||||
|
||||
// Tell sprite_mod to use the texture
|
||||
// Create a sprite rendering pipeline
|
||||
const texture = text_mod.state().texture;
|
||||
sprite_mod.send(.init_pipeline, .{Sprite.PipelineOptions{
|
||||
.pipeline = @intFromEnum(Pipeline.text),
|
||||
.texture = texture,
|
||||
}});
|
||||
const pipeline = try sprite_pipeline.newEntity();
|
||||
try sprite_pipeline.set(pipeline, .texture, texture);
|
||||
sprite_pipeline.send(.update, .{});
|
||||
|
||||
// Run the rest of our init code after sprite_mod's .init_pipeline
|
||||
// TODO(important): relying on this event ordering is not good
|
||||
game.send(.after_sprite_init, .{});
|
||||
}
|
||||
|
||||
fn afterSpriteInit(
|
||||
engine: *mach.Engine.Mod,
|
||||
sprite_mod: *Sprite.Mod,
|
||||
text_mod: *Text.Mod,
|
||||
game: *Mod,
|
||||
) !void {
|
||||
// We can create entities, and set components on them. Note that components live in a module
|
||||
// namespace, e.g. the `Sprite` module could have a 3D `.location` component with a different
|
||||
// type than the `.physics2d` module's `.location` component if you desire.
|
||||
|
||||
const r = text_mod.state().regions.get('?').?;
|
||||
const player = try engine.newEntity();
|
||||
const player = try sprite_mod.newEntity();
|
||||
try sprite_mod.set(player, .transform, Mat4x4.translate(vec3(-0.02, 0, 0)));
|
||||
try sprite_mod.set(player, .size, vec2(@floatFromInt(r.width), @floatFromInt(r.height)));
|
||||
try sprite_mod.set(player, .uv_transform, Mat3x3.translate(vec2(@floatFromInt(r.x), @floatFromInt(r.y))));
|
||||
try sprite_mod.set(player, .pipeline, @intFromEnum(Pipeline.text));
|
||||
sprite_mod.send(.updated, .{@intFromEnum(Pipeline.text)});
|
||||
try sprite_mod.set(player, .pipeline, pipeline);
|
||||
|
||||
game.init(.{
|
||||
.timer = try mach.Timer.start(),
|
||||
|
|
@ -101,12 +82,32 @@ fn afterSpriteInit(
|
|||
.sprites = 0,
|
||||
.rand = std.rand.DefaultPrng.init(1337),
|
||||
.time = 0,
|
||||
.pipeline = pipeline,
|
||||
});
|
||||
|
||||
// TODO(important): text module should not use global init, so that game can instruct it more clearly and then
|
||||
// this after_init would be more clear. Also after_sprite_init should be renamed to after_text_init and the comment
|
||||
// below is wrong:
|
||||
//
|
||||
// Run the rest of our init code after sprite_mod's .init_pipeline
|
||||
game.send(.after_sprite_init, .{});
|
||||
}
|
||||
|
||||
fn afterSpriteInit(
|
||||
sprite_mod: *gfx.Sprite.Mod,
|
||||
text_mod: *Text.Mod,
|
||||
game: *Mod,
|
||||
) !void {
|
||||
const r = text_mod.state().regions.get('?').?;
|
||||
try sprite_mod.set(game.state().player, .size, vec2(@floatFromInt(r.width), @floatFromInt(r.height)));
|
||||
try sprite_mod.set(game.state().player, .uv_transform, Mat3x3.translate(vec2(@floatFromInt(r.x), @floatFromInt(r.y))));
|
||||
sprite_mod.send(.update, .{});
|
||||
}
|
||||
|
||||
fn tick(
|
||||
engine: *mach.Engine.Mod,
|
||||
sprite_mod: *Sprite.Mod,
|
||||
sprite_mod: *gfx.Sprite.Mod,
|
||||
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
||||
text_mod: *Text.Mod,
|
||||
game: *Mod,
|
||||
) !void {
|
||||
|
|
@ -160,7 +161,7 @@ fn tick(
|
|||
try sprite_mod.set(new_entity, .transform, Mat4x4.translate(new_pos).mul(&Mat4x4.scaleScalar(0.3)));
|
||||
try sprite_mod.set(new_entity, .size, vec2(@floatFromInt(r.width), @floatFromInt(r.height)));
|
||||
try sprite_mod.set(new_entity, .uv_transform, Mat3x3.translate(vec2(@floatFromInt(r.x), @floatFromInt(r.y))));
|
||||
try sprite_mod.set(new_entity, .pipeline, @intFromEnum(Pipeline.text));
|
||||
try sprite_mod.set(new_entity, .pipeline, game.state().pipeline);
|
||||
game.state().sprites += 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -204,15 +205,14 @@ fn tick(
|
|||
&Mat4x4.scale(Vec3.splat(1.0)),
|
||||
);
|
||||
try sprite_mod.set(game.state().player, .transform, player_transform);
|
||||
|
||||
sprite_mod.send(.updated, .{@intFromEnum(Pipeline.text)});
|
||||
sprite_mod.send(.update, .{});
|
||||
|
||||
// Perform pre-render work
|
||||
sprite_mod.send(.pre_render, .{@intFromEnum(Pipeline.text)});
|
||||
sprite_pipeline.send(.pre_render, .{});
|
||||
|
||||
// Render a frame
|
||||
engine.send(.begin_pass, .{gpu.Color{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }});
|
||||
sprite_mod.send(.render, .{@intFromEnum(Pipeline.text)});
|
||||
sprite_pipeline.send(.render, .{});
|
||||
engine.send(.end_pass, .{});
|
||||
engine.send(.frame_done, .{}); // Present the frame
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const Text = @import("Text.zig");
|
|||
pub const modules = .{
|
||||
mach.Engine,
|
||||
mach.gfx.Sprite,
|
||||
mach.gfx.SpritePipeline,
|
||||
Text,
|
||||
Game,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const assets = @import("assets");
|
|||
const mach = @import("mach");
|
||||
const core = mach.core;
|
||||
const gpu = mach.gpu;
|
||||
const Sprite = mach.gfx.Sprite;
|
||||
const gfx = mach.gfx;
|
||||
const math = mach.math;
|
||||
|
||||
const vec2 = math.vec2;
|
||||
|
|
@ -28,6 +28,7 @@ sprites: usize,
|
|||
rand: std.rand.DefaultPrng,
|
||||
time: f32,
|
||||
allocator: std.mem.Allocator,
|
||||
pipeline: mach.EntityID,
|
||||
|
||||
const d0 = 0.000001;
|
||||
|
||||
|
|
@ -49,13 +50,10 @@ pub const global_events = .{
|
|||
.tick = .{ .handler = tick },
|
||||
};
|
||||
|
||||
pub const Pipeline = enum(u32) {
|
||||
default,
|
||||
};
|
||||
|
||||
fn init(
|
||||
engine: *mach.Engine.Mod,
|
||||
sprite_mod: *Sprite.Mod,
|
||||
sprite_mod: *gfx.Sprite.Mod,
|
||||
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
||||
game: *Mod,
|
||||
) !void {
|
||||
// The Mach .core is where we set window options, etc.
|
||||
|
|
@ -65,18 +63,19 @@ fn init(
|
|||
// namespace, e.g. the `.mach_gfx_sprite` module could have a 3D `.location` component with a different
|
||||
// type than the `.physics2d` module's `.location` component if you desire.
|
||||
|
||||
// Create a sprite rendering pipeline
|
||||
const allocator = gpa.allocator();
|
||||
const pipeline = try engine.newEntity();
|
||||
try sprite_pipeline.set(pipeline, .texture, try loadTexture(engine, allocator));
|
||||
sprite_pipeline.send(.update, .{});
|
||||
|
||||
// Create our player sprite
|
||||
const player = try engine.newEntity();
|
||||
try sprite_mod.set(player, .transform, Mat4x4.translate(vec3(-0.02, 0, 0)));
|
||||
try sprite_mod.set(player, .size, vec2(32, 32));
|
||||
try sprite_mod.set(player, .uv_transform, Mat3x3.translate(vec2(0, 0)));
|
||||
try sprite_mod.set(player, .pipeline, @intFromEnum(Pipeline.default));
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
sprite_mod.send(.init_pipeline, .{Sprite.PipelineOptions{
|
||||
.pipeline = @intFromEnum(Pipeline.default),
|
||||
.texture = try loadTexture(engine, allocator),
|
||||
}});
|
||||
sprite_mod.send(.updated, .{@intFromEnum(Pipeline.default)});
|
||||
try sprite_mod.set(player, .pipeline, pipeline);
|
||||
sprite_mod.send(.update, .{});
|
||||
|
||||
game.init(.{
|
||||
.timer = try mach.Timer.start(),
|
||||
|
|
@ -88,12 +87,14 @@ fn init(
|
|||
.rand = std.rand.DefaultPrng.init(1337),
|
||||
.time = 0,
|
||||
.allocator = allocator,
|
||||
.pipeline = pipeline,
|
||||
});
|
||||
}
|
||||
|
||||
fn tick(
|
||||
engine: *mach.Engine.Mod,
|
||||
sprite_mod: *Sprite.Mod,
|
||||
sprite_mod: *gfx.Sprite.Mod,
|
||||
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
||||
game: *Mod,
|
||||
) !void {
|
||||
// TODO(engine): event polling should occur in mach.Engine module and get fired as ECS events.
|
||||
|
|
@ -143,7 +144,7 @@ fn tick(
|
|||
try sprite_mod.set(new_entity, .transform, Mat4x4.translate(new_pos).mul(&Mat4x4.scale(Vec3.splat(0.3))));
|
||||
try sprite_mod.set(new_entity, .size, vec2(32, 32));
|
||||
try sprite_mod.set(new_entity, .uv_transform, Mat3x3.translate(vec2(0, 0)));
|
||||
try sprite_mod.set(new_entity, .pipeline, @intFromEnum(Pipeline.default));
|
||||
try sprite_mod.set(new_entity, .pipeline, game.state().pipeline);
|
||||
game.state().sprites += 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -181,14 +182,15 @@ fn tick(
|
|||
player_pos.v[0] += direction.x() * speed * delta_time;
|
||||
player_pos.v[1] += direction.y() * speed * delta_time;
|
||||
try sprite_mod.set(game.state().player, .transform, Mat4x4.translate(player_pos));
|
||||
sprite_mod.send(.updated, .{@intFromEnum(Pipeline.default)});
|
||||
sprite_mod.send(.update, .{});
|
||||
|
||||
// Perform pre-render work
|
||||
sprite_mod.send(.pre_render, .{@intFromEnum(Pipeline.default)});
|
||||
sprite_pipeline.send(.pre_render, .{});
|
||||
|
||||
// Render a frame
|
||||
engine.send(.begin_pass, .{gpu.Color{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }});
|
||||
sprite_mod.send(.render, .{@intFromEnum(Pipeline.default)});
|
||||
|
||||
sprite_pipeline.send(.render, .{});
|
||||
engine.send(.end_pass, .{});
|
||||
engine.send(.frame_done, .{}); // Present the frame
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const Game = @import("Game.zig");
|
|||
pub const modules = .{
|
||||
mach.Engine,
|
||||
mach.gfx.Sprite,
|
||||
mach.gfx.SpritePipeline,
|
||||
Game,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
|||
const mach = @import("../main.zig");
|
||||
const core = mach.core;
|
||||
const gpu = mach.core.gpu;
|
||||
const gfx = mach.gfx;
|
||||
const Engine = mach.Engine;
|
||||
|
||||
const math = mach.math;
|
||||
|
|
@ -11,22 +12,10 @@ const Vec3 = math.Vec3;
|
|||
const Mat3x3 = math.Mat3x3;
|
||||
const Mat4x4 = math.Mat4x4;
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
|
||||
/// Internal state
|
||||
pipelines: std.AutoArrayHashMapUnmanaged(u32, Pipeline) = .{},
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub const name = .mach_gfx_sprite;
|
||||
pub const Mod = mach.Mod(@This());
|
||||
|
||||
pub const components = .{
|
||||
.pipeline = .{ .type = u8, .description =
|
||||
\\ The ID of the pipeline this sprite belongs to. By default, zero.
|
||||
\\
|
||||
\\ This determines which shader, textures, etc. are used for rendering the sprite.
|
||||
},
|
||||
|
||||
.transform = .{ .type = Mat4x4, .description =
|
||||
\\ 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
|
||||
|
|
@ -43,264 +32,45 @@ pub const components = .{
|
|||
.size = .{ .type = Vec2, .description =
|
||||
\\ The size of the sprite, in pixels.
|
||||
},
|
||||
};
|
||||
|
||||
pub const global_events = .{
|
||||
.deinit = .{ .handler = deinit },
|
||||
.init = .{ .handler = init },
|
||||
.pipeline = .{ .type = mach.EntityID, .description =
|
||||
\\ Which render pipeline to use for rendering the sprite.
|
||||
\\
|
||||
\\ This determines which shader, textures, etc. are used for rendering the sprite.
|
||||
},
|
||||
};
|
||||
|
||||
pub const local_events = .{
|
||||
.init_pipeline = .{ .handler = initPipeline },
|
||||
.updated = .{ .handler = updated },
|
||||
.pre_render = .{ .handler = preRender },
|
||||
.render = .{ .handler = render },
|
||||
.update = .{ .handler = update },
|
||||
};
|
||||
|
||||
const Uniforms = extern struct {
|
||||
// WebGPU requires that the size of struct fields are multiples of 16
|
||||
// So we use align(16) and 'extern' to maintain field order
|
||||
|
||||
/// The view * orthographic projection matrix
|
||||
view_projection: Mat4x4 align(16),
|
||||
|
||||
/// Total size of the sprite texture in pixels
|
||||
texture_size: Vec2 align(16),
|
||||
};
|
||||
|
||||
const Pipeline = struct {
|
||||
render: *gpu.RenderPipeline,
|
||||
texture_sampler: *gpu.Sampler,
|
||||
texture: *gpu.Texture,
|
||||
texture2: ?*gpu.Texture,
|
||||
texture3: ?*gpu.Texture,
|
||||
texture4: ?*gpu.Texture,
|
||||
bind_group: *gpu.BindGroup,
|
||||
uniforms: *gpu.Buffer,
|
||||
|
||||
// Storage buffers
|
||||
num_sprites: u32,
|
||||
transforms: *gpu.Buffer,
|
||||
uv_transforms: *gpu.Buffer,
|
||||
sizes: *gpu.Buffer,
|
||||
|
||||
pub fn reference(p: *Pipeline) void {
|
||||
p.render.reference();
|
||||
p.texture_sampler.reference();
|
||||
p.texture.reference();
|
||||
if (p.texture2) |tex| tex.reference();
|
||||
if (p.texture3) |tex| tex.reference();
|
||||
if (p.texture4) |tex| tex.reference();
|
||||
p.bind_group.reference();
|
||||
p.uniforms.reference();
|
||||
p.transforms.reference();
|
||||
p.uv_transforms.reference();
|
||||
p.sizes.reference();
|
||||
fn update(engine: *Engine.Mod, sprite_mod: *Mod, sprite_pipeline: *gfx.SpritePipeline.Mod) !void {
|
||||
var archetypes_iter = sprite_pipeline.entities.query(.{ .all = &.{
|
||||
.{ .mach_gfx_sprite_pipeline = &.{
|
||||
.built,
|
||||
} },
|
||||
} });
|
||||
while (archetypes_iter.next()) |archetype| {
|
||||
const ids = archetype.slice(.entity, .id);
|
||||
const built_pipelines = archetype.slice(.mach_gfx_sprite_pipeline, .built);
|
||||
for (ids, built_pipelines) |pipeline_id, *built| {
|
||||
try updatePipeline(engine, sprite_mod, sprite_pipeline, pipeline_id, built);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(p: *Pipeline) void {
|
||||
p.render.release();
|
||||
p.texture_sampler.release();
|
||||
p.texture.release();
|
||||
if (p.texture2) |tex| tex.release();
|
||||
if (p.texture3) |tex| tex.release();
|
||||
if (p.texture4) |tex| tex.release();
|
||||
p.bind_group.release();
|
||||
p.uniforms.release();
|
||||
p.transforms.release();
|
||||
p.uv_transforms.release();
|
||||
p.sizes.release();
|
||||
}
|
||||
};
|
||||
|
||||
pub const PipelineOptions = struct {
|
||||
pipeline: u32,
|
||||
|
||||
/// Shader program to use when rendering.
|
||||
shader: ?*gpu.ShaderModule = null,
|
||||
|
||||
/// Whether to use linear (blurry) or nearest (pixelated) upscaling/downscaling.
|
||||
texture_sampler: ?*gpu.Sampler = null,
|
||||
|
||||
/// Textures to use when rendering. The default shader can handle one texture.
|
||||
texture: *gpu.Texture,
|
||||
texture2: ?*gpu.Texture = null,
|
||||
texture3: ?*gpu.Texture = null,
|
||||
texture4: ?*gpu.Texture = null,
|
||||
|
||||
/// Alpha and color blending options.
|
||||
blend_state: ?gpu.BlendState = null,
|
||||
|
||||
/// Pipeline overrides, these can be used to e.g. pass additional things to your shader program.
|
||||
bind_group_layout: ?*gpu.BindGroupLayout = null,
|
||||
bind_group: ?*gpu.BindGroup = null,
|
||||
color_target_state: ?gpu.ColorTargetState = null,
|
||||
fragment_state: ?gpu.FragmentState = null,
|
||||
pipeline_layout: ?*gpu.PipelineLayout = null,
|
||||
};
|
||||
|
||||
fn init(sprite_mod: *Mod) void {
|
||||
sprite_mod.init(.{
|
||||
.allocator = gpa.allocator(),
|
||||
});
|
||||
}
|
||||
|
||||
fn deinit(sprite_mod: *Mod) !void {
|
||||
for (sprite_mod.state().pipelines.entries.items(.value)) |*pipeline| pipeline.deinit();
|
||||
sprite_mod.state().pipelines.deinit(sprite_mod.state().allocator);
|
||||
}
|
||||
|
||||
fn initPipeline(
|
||||
fn updatePipeline(
|
||||
engine: *Engine.Mod,
|
||||
sprite_mod: *Mod,
|
||||
opt: PipelineOptions,
|
||||
sprite_pipeline: *gfx.SpritePipeline.Mod,
|
||||
pipeline_id: mach.EntityID,
|
||||
built: *gfx.SpritePipeline.BuiltPipeline,
|
||||
) !void {
|
||||
const device = engine.state().device;
|
||||
const encoder = device.createCommandEncoder(null);
|
||||
defer encoder.release();
|
||||
|
||||
const pipeline = try sprite_mod.state().pipelines.getOrPut(sprite_mod.state().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_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();
|
||||
}
|
||||
|
||||
fn updated(
|
||||
engine: *Engine.Mod,
|
||||
sprite_mod: *Mod,
|
||||
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 = &.{
|
||||
var archetypes_iter = sprite_mod.entities.query(.{ .all = &.{
|
||||
.{ .mach_gfx_sprite = &.{
|
||||
.uv_transform,
|
||||
.transform,
|
||||
|
|
@ -308,77 +78,38 @@ fn updated(
|
|||
.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;
|
||||
var num_sprites: u32 = 0;
|
||||
var i: usize = 0;
|
||||
while (archetypes_iter.next()) |archetype| {
|
||||
const transforms = archetype.slice(.mach_gfx_sprite, .transform);
|
||||
const uv_transforms = archetype.slice(.mach_gfx_sprite, .uv_transform);
|
||||
const sizes = archetype.slice(.mach_gfx_sprite, .size);
|
||||
const pipelines = archetype.slice(.mach_gfx_sprite, .pipeline);
|
||||
|
||||
// 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);
|
||||
// TODO: currently we cannot query all sprites which have a _single_ pipeline component
|
||||
// value and get back contiguous memory for all of them. This is because all sprites with
|
||||
// possibly different pipeline component values are stored as the same archetype. If we
|
||||
// introduce a new concept of tagging-by-value to our entity storage then we can enforce
|
||||
// that all entities with the same pipeline value are stored in contiguous memory, and
|
||||
// skip this copy.
|
||||
for (transforms, uv_transforms, sizes, pipelines) |transform, uv_transform, size, sprite_pipeline_id| {
|
||||
if (sprite_pipeline_id == pipeline_id) {
|
||||
gfx.SpritePipeline.cp_transforms[i] = transform;
|
||||
gfx.SpritePipeline.cp_uv_transforms[i] = uv_transform;
|
||||
gfx.SpritePipeline.cp_sizes[i] = size;
|
||||
i += 1;
|
||||
num_sprites += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try sprite_pipeline.set(pipeline_id, .num_sprites, num_sprites);
|
||||
if (num_sprites > 0) {
|
||||
encoder.writeBuffer(built.transforms, 0, gfx.SpritePipeline.cp_transforms[0..i]);
|
||||
encoder.writeBuffer(built.uv_transforms, 0, gfx.SpritePipeline.cp_uv_transforms[0..i]);
|
||||
encoder.writeBuffer(built.sizes, 0, gfx.SpritePipeline.cp_sizes[0..i]);
|
||||
var command = encoder.finish(null);
|
||||
defer command.release();
|
||||
|
||||
engine.state().queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||
}
|
||||
|
||||
fn preRender(
|
||||
engine: *Engine.Mod,
|
||||
sprite_mod: *Mod,
|
||||
pipeline_id: u32,
|
||||
) !void {
|
||||
const pipeline = sprite_mod.state().pipelines.get(pipeline_id).?;
|
||||
|
||||
// Update uniform buffer
|
||||
const proj = Mat4x4.projection2D(.{
|
||||
.left = -@as(f32, @floatFromInt(core.size().width)) / 2,
|
||||
.right = @as(f32, @floatFromInt(core.size().width)) / 2,
|
||||
.bottom = -@as(f32, @floatFromInt(core.size().height)) / 2,
|
||||
.top = @as(f32, @floatFromInt(core.size().height)) / 2,
|
||||
.near = -0.1,
|
||||
.far = 100000,
|
||||
});
|
||||
const uniforms = Uniforms{
|
||||
.view_projection = proj,
|
||||
// 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});
|
||||
}
|
||||
|
||||
fn render(
|
||||
engine: *Engine.Mod,
|
||||
sprite_mod: *Mod,
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
373
src/gfx/SpritePipeline.zig
Normal file
373
src/gfx/SpritePipeline.zig
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
const std = @import("std");
|
||||
const mach = @import("../main.zig");
|
||||
const core = mach.core;
|
||||
|
||||
const gpu = mach.gpu;
|
||||
const math = mach.math;
|
||||
|
||||
pub const name = .mach_gfx_sprite_pipeline;
|
||||
pub const Mod = mach.Mod(@This());
|
||||
|
||||
pub const components = .{
|
||||
.texture = .{ .type = *gpu.Texture, .description =
|
||||
\\ Texture to use when rendering. The default shader can handle only one texture input.
|
||||
\\ Must be specified for a pipeline entity to be valid.
|
||||
},
|
||||
|
||||
.texture2 = .{ .type = *gpu.Texture },
|
||||
.texture3 = .{ .type = *gpu.Texture },
|
||||
.texture4 = .{ .type = *gpu.Texture },
|
||||
|
||||
.shader = .{ .type = *gpu.ShaderModule, .description =
|
||||
\\ Shader program to use when rendering
|
||||
\\ Defaults to sprite.wgsl
|
||||
},
|
||||
|
||||
.texture_sampler = .{ .type = *gpu.Sampler, .description =
|
||||
\\ Whether to use linear (blurry) or nearest (pixelated) upscaling/downscaling.
|
||||
\\ Defaults to nearest (pixelated)
|
||||
},
|
||||
|
||||
.blend_state = .{ .type = gpu.BlendState, .description =
|
||||
\\ Alpha and color blending options
|
||||
\\ Defaults to
|
||||
\\ .{
|
||||
\\ .color = .{ .operation = .add, .src_factor = .src_alpha .dst_factor = .one_minus_src_alpha },
|
||||
\\ .alpha = .{ .operation = .add, .src_factor = .one, .dst_factor = .zero },
|
||||
\\ }
|
||||
},
|
||||
|
||||
.bind_group_layout = .{ .type = *gpu.BindGroupLayout, .description =
|
||||
\\ Override to enable passing additional data to your shader program.
|
||||
},
|
||||
.bind_group = .{ .type = *gpu.BindGroup, .description =
|
||||
\\ Override to enable passing additional data to your shader program.
|
||||
},
|
||||
.color_target_state = .{ .type = gpu.ColorTargetState, .description =
|
||||
\\ Override to enable custom color target state for render pipeline.
|
||||
},
|
||||
.fragment_state = .{ .type = gpu.FragmentState, .description =
|
||||
\\ Override to enable custom fragment state for render pipeline.
|
||||
},
|
||||
.layout = .{ .type = *gpu.PipelineLayout, .description =
|
||||
\\ Override to enable custom pipeline layout.
|
||||
},
|
||||
|
||||
.num_sprites = .{ .type = u32, .description =
|
||||
\\ Number of sprites this pipeline will render.
|
||||
\\ Read-only, updated as part of Sprite.update
|
||||
},
|
||||
.built = .{ .type = BuiltPipeline, .description = "internal" },
|
||||
};
|
||||
|
||||
pub const global_events = .{
|
||||
.deinit = .{ .handler = deinit },
|
||||
};
|
||||
|
||||
pub const local_events = .{
|
||||
.update = .{ .handler = update },
|
||||
.pre_render = .{ .handler = preRender },
|
||||
.render = .{ .handler = render },
|
||||
};
|
||||
|
||||
const Uniforms = extern struct {
|
||||
// WebGPU requires that the size of struct fields are multiples of 16
|
||||
// So we use align(16) and 'extern' to maintain field order
|
||||
|
||||
/// The view * orthographic projection matrix
|
||||
view_projection: math.Mat4x4 align(16),
|
||||
|
||||
/// Total size of the sprite texture in pixels
|
||||
texture_size: math.Vec2 align(16),
|
||||
};
|
||||
|
||||
const sprite_buffer_cap = 1024 * 512; // TODO(sprite): allow user to specify preallocation
|
||||
|
||||
// TODO(sprite): eliminate these, see Sprite.updatePipeline for details on why these exist
|
||||
// currently.
|
||||
pub var cp_transforms: [sprite_buffer_cap]math.Mat4x4 = undefined;
|
||||
pub var cp_uv_transforms: [sprite_buffer_cap]math.Mat3x3 = undefined;
|
||||
pub var cp_sizes: [sprite_buffer_cap]math.Vec2 = undefined;
|
||||
|
||||
pub const BuiltPipeline = struct {
|
||||
render: *gpu.RenderPipeline,
|
||||
texture_sampler: *gpu.Sampler,
|
||||
texture: *gpu.Texture,
|
||||
texture2: ?*gpu.Texture,
|
||||
texture3: ?*gpu.Texture,
|
||||
texture4: ?*gpu.Texture,
|
||||
bind_group: *gpu.BindGroup,
|
||||
uniforms: *gpu.Buffer,
|
||||
|
||||
// Storage buffers
|
||||
transforms: *gpu.Buffer,
|
||||
uv_transforms: *gpu.Buffer,
|
||||
sizes: *gpu.Buffer,
|
||||
|
||||
pub fn reference(p: *BuiltPipeline) void {
|
||||
p.render.reference();
|
||||
p.texture_sampler.reference();
|
||||
p.texture.reference();
|
||||
if (p.texture2) |tex| tex.reference();
|
||||
if (p.texture3) |tex| tex.reference();
|
||||
if (p.texture4) |tex| tex.reference();
|
||||
p.bind_group.reference();
|
||||
p.uniforms.reference();
|
||||
p.transforms.reference();
|
||||
p.uv_transforms.reference();
|
||||
p.sizes.reference();
|
||||
}
|
||||
|
||||
pub fn deinit(p: *BuiltPipeline) void {
|
||||
p.render.release();
|
||||
p.texture_sampler.release();
|
||||
p.texture.release();
|
||||
if (p.texture2) |tex| tex.release();
|
||||
if (p.texture3) |tex| tex.release();
|
||||
if (p.texture4) |tex| tex.release();
|
||||
p.bind_group.release();
|
||||
p.uniforms.release();
|
||||
p.transforms.release();
|
||||
p.uv_transforms.release();
|
||||
p.sizes.release();
|
||||
}
|
||||
};
|
||||
|
||||
fn deinit(sprite_pipeline: *Mod) void {
|
||||
var archetypes_iter = sprite_pipeline.entities.query(.{ .all = &.{
|
||||
.{ .mach_gfx_sprite_pipeline = &.{
|
||||
.built,
|
||||
} },
|
||||
} });
|
||||
while (archetypes_iter.next()) |archetype| {
|
||||
for (archetype.slice(.mach_gfx_sprite_pipeline, .built)) |*p| p.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
fn update(engine: *mach.Engine.Mod, sprite_pipeline: *Mod) !void {
|
||||
// Destroy all sprite render pipelines. We will rebuild them all.
|
||||
deinit(sprite_pipeline);
|
||||
|
||||
var archetypes_iter = sprite_pipeline.entities.query(.{ .all = &.{
|
||||
.{ .mach_gfx_sprite_pipeline = &.{
|
||||
.texture,
|
||||
} },
|
||||
} });
|
||||
while (archetypes_iter.next()) |archetype| {
|
||||
const ids = archetype.slice(.entity, .id);
|
||||
const textures = archetype.slice(.mach_gfx_sprite_pipeline, .texture);
|
||||
|
||||
for (ids, textures) |pipeline_id, texture| {
|
||||
try buildPipeline(engine, sprite_pipeline, pipeline_id, texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buildPipeline(
|
||||
engine: *mach.Engine.Mod,
|
||||
sprite_pipeline: *Mod,
|
||||
pipeline_id: mach.EntityID,
|
||||
texture: *gpu.Texture,
|
||||
) !void {
|
||||
const opt_texture2 = sprite_pipeline.get(pipeline_id, .texture2);
|
||||
const opt_texture3 = sprite_pipeline.get(pipeline_id, .texture3);
|
||||
const opt_texture4 = sprite_pipeline.get(pipeline_id, .texture4);
|
||||
const opt_shader = sprite_pipeline.get(pipeline_id, .shader);
|
||||
const opt_texture_sampler = sprite_pipeline.get(pipeline_id, .texture_sampler);
|
||||
const opt_blend_state = sprite_pipeline.get(pipeline_id, .blend_state);
|
||||
const opt_bind_group_layout = sprite_pipeline.get(pipeline_id, .bind_group_layout);
|
||||
const opt_bind_group = sprite_pipeline.get(pipeline_id, .bind_group);
|
||||
const opt_color_target_state = sprite_pipeline.get(pipeline_id, .color_target_state);
|
||||
const opt_fragment_state = sprite_pipeline.get(pipeline_id, .fragment_state);
|
||||
const opt_layout = sprite_pipeline.get(pipeline_id, .layout);
|
||||
|
||||
const device = engine.state().device;
|
||||
|
||||
// Storage buffers
|
||||
const transforms = device.createBuffer(&.{
|
||||
.usage = .{ .storage = true, .copy_dst = true },
|
||||
.size = @sizeOf(math.Mat4x4) * sprite_buffer_cap,
|
||||
.mapped_at_creation = .false,
|
||||
});
|
||||
const uv_transforms = device.createBuffer(&.{
|
||||
.usage = .{ .storage = true, .copy_dst = true },
|
||||
.size = @sizeOf(math.Mat3x3) * sprite_buffer_cap,
|
||||
.mapped_at_creation = .false,
|
||||
});
|
||||
const sizes = device.createBuffer(&.{
|
||||
.usage = .{ .storage = true, .copy_dst = true },
|
||||
.size = @sizeOf(math.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 = 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(math.Mat4x4) * sprite_buffer_cap),
|
||||
gpu.BindGroup.Entry.buffer(2, uv_transforms, 0, @sizeOf(math.Mat3x3) * sprite_buffer_cap),
|
||||
gpu.BindGroup.Entry.buffer(3, sizes, 0, @sizeOf(math.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_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",
|
||||
},
|
||||
});
|
||||
|
||||
var built = BuiltPipeline{
|
||||
.render = render_pipeline,
|
||||
.texture_sampler = texture_sampler,
|
||||
.texture = texture,
|
||||
.texture2 = opt_texture2,
|
||||
.texture3 = opt_texture3,
|
||||
.texture4 = opt_texture4,
|
||||
.bind_group = bind_group,
|
||||
.uniforms = uniforms,
|
||||
.transforms = transforms,
|
||||
.uv_transforms = uv_transforms,
|
||||
.sizes = sizes,
|
||||
};
|
||||
built.reference();
|
||||
try sprite_pipeline.set(pipeline_id, .built, built);
|
||||
try sprite_pipeline.set(pipeline_id, .num_sprites, 0);
|
||||
}
|
||||
|
||||
fn preRender(
|
||||
engine: *mach.Engine.Mod,
|
||||
sprite_pipeline: *Mod,
|
||||
) void {
|
||||
var archetypes_iter = sprite_pipeline.entities.query(.{ .all = &.{
|
||||
.{ .mach_gfx_sprite_pipeline = &.{
|
||||
.built,
|
||||
} },
|
||||
} });
|
||||
while (archetypes_iter.next()) |archetype| {
|
||||
const built_pipelines = archetype.slice(.mach_gfx_sprite_pipeline, .built);
|
||||
for (built_pipelines) |built| {
|
||||
// Create the projection matrix
|
||||
// TODO(sprite): move this out of the hot codepath
|
||||
const proj = math.Mat4x4.projection2D(.{
|
||||
.left = -@as(f32, @floatFromInt(core.size().width)) / 2,
|
||||
.right = @as(f32, @floatFromInt(core.size().width)) / 2,
|
||||
.bottom = -@as(f32, @floatFromInt(core.size().height)) / 2,
|
||||
.top = @as(f32, @floatFromInt(core.size().height)) / 2,
|
||||
.near = -0.1,
|
||||
.far = 100000,
|
||||
});
|
||||
|
||||
// Update uniform buffer
|
||||
const uniforms = Uniforms{
|
||||
.view_projection = proj,
|
||||
// TODO(sprite): dimensions of other textures, number of textures present
|
||||
.texture_size = math.vec2(
|
||||
@as(f32, @floatFromInt(built.texture.getWidth())),
|
||||
@as(f32, @floatFromInt(built.texture.getHeight())),
|
||||
),
|
||||
};
|
||||
engine.state().encoder.writeBuffer(built.uniforms, 0, &[_]Uniforms{uniforms});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
engine: *mach.Engine.Mod,
|
||||
sprite_pipeline: *Mod,
|
||||
) !void {
|
||||
var archetypes_iter = sprite_pipeline.entities.query(.{ .all = &.{
|
||||
.{ .mach_gfx_sprite_pipeline = &.{
|
||||
.built,
|
||||
} },
|
||||
} });
|
||||
while (archetypes_iter.next()) |archetype| {
|
||||
const ids = archetype.slice(.entity, .id);
|
||||
const built_pipelines = archetype.slice(.mach_gfx_sprite_pipeline, .built);
|
||||
for (ids, built_pipelines) |pipeline_id, built| {
|
||||
// Draw the sprite batch
|
||||
const pass = engine.state().pass;
|
||||
const total_vertices = sprite_pipeline.get(pipeline_id, .num_sprites).? * 6;
|
||||
pass.setPipeline(built.render);
|
||||
// TODO(sprite): remove dynamic offsets?
|
||||
pass.setBindGroup(0, built.bind_group, &.{});
|
||||
pass.draw(total_vertices, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ pub const Atlas = @import("atlas/Atlas.zig");
|
|||
|
||||
// ECS modules
|
||||
pub const Sprite = @import("Sprite.zig");
|
||||
pub const SpritePipeline = @import("SpritePipeline.zig");
|
||||
pub const Text = @import("Text.zig");
|
||||
pub const TextStyle = @import("TextStyle.zig");
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ pub const modules = blk: {
|
|||
pub const ModSet = @import("module/main.zig").ModSet;
|
||||
pub const Modules = @import("module/main.zig").Modules(modules);
|
||||
pub const Mod = ModSet(modules).Mod;
|
||||
pub const EntityID = @import("module/main.zig").EntityID;
|
||||
pub const EntityID = @import("module/main.zig").EntityID; // TODO: rename to just Entity?
|
||||
pub const Archetype = @import("module/main.zig").Archetype;
|
||||
|
||||
test {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue