add drawing texture support
This commit is contained in:
parent
47d3dad1c3
commit
76668e841d
20
src/app.zig
20
src/app.zig
@ -17,14 +17,9 @@ bg: Platform.Color,
|
||||
show_first_window: bool,
|
||||
check: bool,
|
||||
player_pos: Vec2,
|
||||
tex: Gfx.TextureId,
|
||||
|
||||
pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
self.* = App{
|
||||
.bg = .black,
|
||||
.player_pos = .init(100, 100),
|
||||
.show_first_window = true,
|
||||
.check = false,
|
||||
};
|
||||
const cwd = std.Io.Dir.cwd();
|
||||
const sokoban_spritesheet = try cwd.readFileAlloc(
|
||||
plt.io,
|
||||
@ -37,6 +32,17 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
var image = try STBImage.load(sokoban_spritesheet);
|
||||
defer image.deinit();
|
||||
|
||||
const tex = try Gfx.initTexture();
|
||||
try Gfx.setTexture(tex, image.rgba8_pixels, image.width, image.height);
|
||||
|
||||
self.* = App{
|
||||
.bg = .black,
|
||||
.player_pos = .init(100, 100),
|
||||
.show_first_window = true,
|
||||
.check = false,
|
||||
.tex = tex
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -62,7 +68,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||
self.player_pos = self.player_pos.add(dir.multiplyScalar(50 * dt));
|
||||
|
||||
Gfx.setClearColor(self.bg);
|
||||
Gfx.drawRectangle(self.player_pos, .init(100, 100), .rgb(200, 0, 0));
|
||||
Gfx.drawTexture(self.tex, self.player_pos, .init(100, 100), .rgb(200, 0, 0));
|
||||
Gfx.drawRectangle(self.player_pos.add(.init(0, 200)), .init(100, 100), .rgb(200, 200, 0));
|
||||
|
||||
if (ImGUI.beginWindow(.{
|
||||
|
||||
231
src/graphics.zig
231
src/graphics.zig
@ -16,25 +16,32 @@ const Color = @import("./color.zig");
|
||||
const shd = @import("shader");
|
||||
const SlotMapType = @import("slot_map.zig").SlotMapType;
|
||||
|
||||
|
||||
const State = struct {
|
||||
gpa: std.mem.Allocator,
|
||||
|
||||
shader: sg.Shader,
|
||||
pipeline: sg.Pipeline,
|
||||
bindings: sg.Bindings,
|
||||
|
||||
default_image: sg.Image,
|
||||
default_view: sg.View,
|
||||
default_texture: TextureId,
|
||||
default_sampler: sg.Sampler,
|
||||
|
||||
quads_buffer: [2048]Quad,
|
||||
quads: std.ArrayList(Quad),
|
||||
|
||||
clear_color: Color,
|
||||
|
||||
textures_buffer: [max_textures]Texture,
|
||||
textures_slotmap: TextureSlotMap,
|
||||
};
|
||||
|
||||
const max_textures = 64;
|
||||
const TextureSlotMap = SlotMapType(std.math.IntFittingRange(0, max_textures-1), u8);
|
||||
pub const TextureId = TextureSlotMap.Id;
|
||||
|
||||
var g_state: State = undefined;
|
||||
|
||||
pub fn init(logger: sg.Logger) void {
|
||||
pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
||||
const self = &g_state;
|
||||
|
||||
sg.setup(.{
|
||||
@ -45,27 +52,6 @@ pub fn init(logger: sg.Logger) void {
|
||||
const shader = sg.makeShader(shd.mainShaderDesc(sg.queryBackend()));
|
||||
assert(shader.id != sg.invalid_id);
|
||||
|
||||
var pixels: [8 * 8 * 4]u8 = undefined;
|
||||
@memset(&pixels, 0xFF);
|
||||
var image_data: sg.ImageData = .{};
|
||||
image_data.mip_levels[0] = sg.asRange(&pixels);
|
||||
const default_image = sg.makeImage(.{
|
||||
.type = ._2D,
|
||||
.width = 8,
|
||||
.height = 8,
|
||||
.num_mipmaps = 1,
|
||||
.pixel_format = .RGBA8,
|
||||
.data = image_data,
|
||||
.label = "default-texture"
|
||||
});
|
||||
assert(default_image.id != sg.invalid_id);
|
||||
|
||||
const default_view = sg.makeView(.{
|
||||
.texture = .{ .image = default_image },
|
||||
.label = "default-view"
|
||||
});
|
||||
assert(default_view.id != sg.invalid_id);
|
||||
|
||||
const default_sampler = sg.makeSampler(.{
|
||||
.min_filter = .NEAREST,
|
||||
.mag_filter = .NEAREST,
|
||||
@ -109,7 +95,7 @@ pub fn init(logger: sg.Logger) void {
|
||||
|
||||
bindings.vertex_buffers[0] = sg.makeBuffer(.{
|
||||
.size = @sizeOf(Quad) * max_quads,
|
||||
.usage = .{ .vertex_buffer = true, .dynamic_update = true },
|
||||
.usage = .{ .vertex_buffer = true, .stream_update = true },
|
||||
.label = "quad-vertices"
|
||||
});
|
||||
|
||||
@ -133,25 +119,38 @@ pub fn init(logger: sg.Logger) void {
|
||||
}
|
||||
|
||||
self.* = State{
|
||||
.gpa = gpa,
|
||||
.shader = shader,
|
||||
.pipeline = pipeline,
|
||||
.bindings = bindings,
|
||||
.default_image = default_image,
|
||||
.default_view = default_view,
|
||||
.default_sampler = default_sampler,
|
||||
.quads_buffer = undefined,
|
||||
.quads = .initBuffer(&self.quads_buffer),
|
||||
.clear_color = .black
|
||||
.clear_color = .black,
|
||||
.textures_buffer = undefined,
|
||||
.textures_slotmap = .init(try .initCapacity(gpa, max_textures)),
|
||||
.default_texture = undefined
|
||||
};
|
||||
|
||||
{
|
||||
const width = 8;
|
||||
const height = 8;
|
||||
|
||||
var pixels: [width * height * 4]u8 = undefined;
|
||||
@memset(&pixels, 0xFF);
|
||||
|
||||
self.default_texture = try initTexture();
|
||||
try setTexture(self.default_texture, &pixels, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
const self = &g_state;
|
||||
|
||||
self.textures_slotmap.deinit(self.gpa);
|
||||
|
||||
sg.destroyPipeline(self.pipeline);
|
||||
sg.destroyShader(self.shader);
|
||||
sg.destroyImage(self.default_image);
|
||||
sg.destroyView(self.default_view);
|
||||
sg.destroySampler(self.default_sampler);
|
||||
sg.shutdown();
|
||||
}
|
||||
@ -197,6 +196,11 @@ pub fn beginFrame() void {
|
||||
.action = pass_action,
|
||||
.swapchain = sglue.swapchain()
|
||||
});
|
||||
|
||||
assert(self.textures_slotmap.exists(self.default_texture));
|
||||
const default_texture = self.textures_buffer[self.default_texture.index];
|
||||
self.bindings.views[shd.VIEW_tex] = default_texture.view;
|
||||
self.bindings.samplers[shd.SMP_smp] = self.default_sampler;
|
||||
}
|
||||
|
||||
// TODO: This will always have a 1-frame delay.
|
||||
@ -213,11 +217,17 @@ pub fn flush() void {
|
||||
if (self.quads.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
defer self.quads.clearRetainingCapacity();
|
||||
|
||||
sg.updateBuffer(
|
||||
self.bindings.vertex_buffers[0],
|
||||
sg.asRange(self.quads.items)
|
||||
);
|
||||
const vertex_buffer = self.bindings.vertex_buffers[0];
|
||||
|
||||
const data = sg.asRange(self.quads.items);
|
||||
if (sg.queryBufferWillOverflow(vertex_buffer, data.size)) {
|
||||
log.warn("Vertex buffer overflow", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
self.bindings.vertex_buffer_offsets[0] = sg.appendBuffer(vertex_buffer, data);
|
||||
|
||||
// TODO: Rectify `window_size` vs `gfx.window_size`
|
||||
const window_size = Vec2.init(sapp.widthf(), sapp.heightf());
|
||||
@ -229,15 +239,10 @@ pub fn flush() void {
|
||||
.projection = createProjectionMatrix(window_size)
|
||||
};
|
||||
|
||||
self.bindings.views[shd.VIEW_tex] = self.default_view;
|
||||
self.bindings.samplers[shd.SMP_smp] = self.default_sampler;
|
||||
|
||||
sg.applyPipeline(self.pipeline);
|
||||
sg.applyBindings(self.bindings);
|
||||
sg.applyUniforms(shd.UB_vs_params, sg.asRange(&vs_params));
|
||||
sg.draw(0, @intCast(self.quads.items.len * 6), 1);
|
||||
|
||||
self.quads.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn endFrame() void {
|
||||
@ -258,34 +263,9 @@ pub fn drawQuad(quad: Quad) void {
|
||||
}
|
||||
|
||||
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
|
||||
const color_vec4 = Vec4{
|
||||
.x = color.r,
|
||||
.y = color.g,
|
||||
.z = color.b,
|
||||
.w = color.a,
|
||||
};
|
||||
drawQuad(Quad{
|
||||
Vertex{
|
||||
.position = pos,
|
||||
.color = color_vec4,
|
||||
.texcoord = .init(0, 0)
|
||||
},
|
||||
Vertex{
|
||||
.position = pos.add(.{ .x = size.x, .y = 0 }),
|
||||
.color = color_vec4,
|
||||
.texcoord = .init(1, 0)
|
||||
},
|
||||
Vertex{
|
||||
.position = pos.add(.{ .x = 0, .y = size.y }),
|
||||
.color = color_vec4,
|
||||
.texcoord = .init(0, 1)
|
||||
},
|
||||
Vertex{
|
||||
.position = pos.add(size),
|
||||
.color = color_vec4,
|
||||
.texcoord = .init(1, 1)
|
||||
},
|
||||
});
|
||||
const self = &g_state;
|
||||
|
||||
drawTexture(self.default_texture, pos, size, color);
|
||||
}
|
||||
|
||||
pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
@ -320,6 +300,121 @@ pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn initTexture() !TextureId {
|
||||
const self = &g_state;
|
||||
const id = try self.textures_slotmap.insertBounded();
|
||||
|
||||
const image = sg.allocImage();
|
||||
assert(image.id != sg.invalid_id);
|
||||
|
||||
const view = sg.allocView();
|
||||
assert(view.id != sg.invalid_id);
|
||||
|
||||
const texture = &self.textures_buffer[id.index];
|
||||
texture.* = .{
|
||||
.image = image,
|
||||
.view = view
|
||||
};
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn deinitTexture(id: TextureId) void {
|
||||
const self = &g_state;
|
||||
if (self.textures_slotmap.remove(id)) {
|
||||
const texture = self.textures_buffer[id.index];
|
||||
const bound_view = &self.bindings.views[shd.VIEW_tex];
|
||||
if (texture.view.id == bound_view.id) {
|
||||
bound_view.* = self.default_view;
|
||||
}
|
||||
|
||||
sg.destroyView(texture.view);
|
||||
sg.destroyImage(texture.image);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setTexture(id: TextureId, rgba8_pixels: [*]const u8, width: u32, height: u32) !void {
|
||||
const self = &g_state;
|
||||
if (!self.textures_slotmap.exists(id)) {
|
||||
return error.NotFound;
|
||||
}
|
||||
const texture = &self.textures_buffer[id.index];
|
||||
|
||||
if (sg.queryImageState(texture.image) == .VALID) {
|
||||
sg.uninitImage(texture.image);
|
||||
}
|
||||
|
||||
const pixel_count = width*height;
|
||||
|
||||
var image_data: sg.ImageData = .{};
|
||||
image_data.mip_levels[0] = sg.asRange(rgba8_pixels[0..(pixel_count*4)]);
|
||||
sg.initImage(texture.image, .{
|
||||
.type = ._2D,
|
||||
.width = @intCast(width),
|
||||
.height = @intCast(height),
|
||||
.num_mipmaps = 1,
|
||||
.pixel_format = .RGBA8,
|
||||
.data = image_data,
|
||||
});
|
||||
assert(sg.queryImageState(texture.image) == .VALID);
|
||||
|
||||
if (sg.queryViewState(texture.view) == .ALLOC) {
|
||||
sg.initView(texture.view, .{
|
||||
.texture = .{ .image = texture.image }
|
||||
});
|
||||
assert(sg.queryViewState(texture.view) == .VALID);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawTexture(id: TextureId, pos: Vec2, size: Vec2, color: Color) void {
|
||||
const self = &g_state;
|
||||
if (!self.textures_slotmap.exists(id)) {
|
||||
// TODO: Draw purple-black texture
|
||||
return;
|
||||
}
|
||||
|
||||
const texture = self.textures_buffer[id.index];
|
||||
|
||||
const bound_view = &self.bindings.views[shd.VIEW_tex];
|
||||
if (bound_view.id != texture.view.id) {
|
||||
flush();
|
||||
bound_view.* = texture.view;
|
||||
}
|
||||
|
||||
const color_vec4 = Vec4{
|
||||
.x = color.r,
|
||||
.y = color.g,
|
||||
.z = color.b,
|
||||
.w = color.a,
|
||||
};
|
||||
drawQuad(Quad{
|
||||
Vertex{
|
||||
.position = pos,
|
||||
.color = color_vec4,
|
||||
.texcoord = .init(0, 0)
|
||||
},
|
||||
Vertex{
|
||||
.position = pos.add(.{ .x = size.x, .y = 0 }),
|
||||
.color = color_vec4,
|
||||
.texcoord = .init(1, 0)
|
||||
},
|
||||
Vertex{
|
||||
.position = pos.add(.{ .x = 0, .y = size.y }),
|
||||
.color = color_vec4,
|
||||
.texcoord = .init(0, 1)
|
||||
},
|
||||
Vertex{
|
||||
.position = pos.add(size),
|
||||
.color = color_vec4,
|
||||
.texcoord = .init(1, 1)
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const Texture = struct {
|
||||
image: sg.Image,
|
||||
view: sg.View,
|
||||
};
|
||||
|
||||
// const SlotMap = SlotMapType(u16, u8);
|
||||
//
|
||||
// const TextureId = u64;
|
||||
|
||||
@ -18,6 +18,7 @@ const ig = @import("cimgui");
|
||||
const ImGUI = @This();
|
||||
|
||||
const State = struct {
|
||||
enabled: bool,
|
||||
frame: ?Allocator
|
||||
};
|
||||
|
||||
@ -40,6 +41,7 @@ pub fn init(logger: simgui.Logger) void {
|
||||
}
|
||||
|
||||
self.* = State{
|
||||
.enabled = true,
|
||||
.frame = null
|
||||
};
|
||||
}
|
||||
@ -57,10 +59,15 @@ pub fn event(e: sapp.Event) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return simgui.handleEvent(e);
|
||||
}
|
||||
|
||||
pub fn beginFrame(frame: Allocator) void {
|
||||
pub fn beginFrame(enabled: bool, frame: Allocator) void {
|
||||
if (!build_options.has_imgui) {
|
||||
return;
|
||||
}
|
||||
@ -68,6 +75,11 @@ pub fn beginFrame(frame: Allocator) void {
|
||||
const self = &g_state;
|
||||
|
||||
self.frame = frame;
|
||||
self.enabled = enabled;
|
||||
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
simgui.newFrame(.{
|
||||
.width = sapp.width(),
|
||||
@ -105,6 +117,10 @@ pub fn endFrame() void {
|
||||
if (!build_options.has_imgui) {
|
||||
return;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// if (ig.igBegin("Game", null, ig.ImGuiWindowFlags_None)) {
|
||||
@ -140,6 +156,10 @@ pub fn beginWindow(opts: WindowOptions) bool {
|
||||
if (!build_options.has_imgui) {
|
||||
return false;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.open != null and opts.open.?.* == false) {
|
||||
return false;
|
||||
@ -180,6 +200,10 @@ pub fn endWindow() void {
|
||||
if (!build_options.has_imgui) {
|
||||
return;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEnd();
|
||||
}
|
||||
@ -196,6 +220,10 @@ pub fn text(comptime fmt: []const u8, args: anytype) void {
|
||||
if (!build_options.has_imgui) {
|
||||
return;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igTextUnformatted(formatString(fmt, args));
|
||||
}
|
||||
@ -204,6 +232,10 @@ pub fn button(label: []const u8) bool {
|
||||
if (!build_options.has_imgui) {
|
||||
return false;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igButton(formatString("{s}", .{label}));
|
||||
}
|
||||
@ -219,6 +251,10 @@ pub fn slider(opts: SliderOptions) bool {
|
||||
if (!build_options.has_imgui) {
|
||||
return false;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igSliderFloat(
|
||||
formatString("{s}", .{opts.label}),
|
||||
@ -232,6 +268,10 @@ pub fn checkbox(label: []const u8, value: *bool) bool {
|
||||
if (!build_options.has_imgui) {
|
||||
return false;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igCheckbox(
|
||||
formatString("{s}", .{label}),
|
||||
@ -243,6 +283,10 @@ pub fn separator() void {
|
||||
if (!build_options.has_imgui) {
|
||||
return;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igSeparator();
|
||||
}
|
||||
@ -257,6 +301,10 @@ pub fn colorEdit(opts: ColorEditOptions) bool {
|
||||
if (!build_options.has_imgui) {
|
||||
return false;
|
||||
}
|
||||
const self = &g_state;
|
||||
if (!self.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const label = formatString("{s}", .{opts.label});
|
||||
var color: [4]f32 = .{
|
||||
|
||||
@ -24,7 +24,7 @@ const shd = @import("shader");
|
||||
|
||||
pub const ImGUI = @import("./imgui.zig");
|
||||
pub const Gfx = @import("./graphics.zig");
|
||||
const Input = @import("./input.zig");
|
||||
pub const Input = @import("./input.zig");
|
||||
|
||||
fn PlatformType(App: type) type {
|
||||
return struct {
|
||||
@ -57,7 +57,7 @@ fn PlatformType(App: type) type {
|
||||
self.last_frame_at = self.started_at;
|
||||
self.input = Input.init(window_size);
|
||||
|
||||
Gfx.init(.{
|
||||
try Gfx.init(self.gpa, .{
|
||||
.func = sokolLogCallback
|
||||
});
|
||||
ImGUI.init(.{
|
||||
@ -103,9 +103,7 @@ fn PlatformType(App: type) type {
|
||||
|
||||
{
|
||||
Gfx.beginFrame();
|
||||
if (self.show_imgui) {
|
||||
ImGUI.beginFrame(self.frame_arena.allocator());
|
||||
}
|
||||
ImGUI.beginFrame(self.show_imgui, self.frame_arena.allocator());
|
||||
|
||||
if (@hasDecl(App, "frame")) {
|
||||
const plt = Frame{
|
||||
@ -121,9 +119,7 @@ fn PlatformType(App: type) type {
|
||||
}
|
||||
|
||||
Gfx.flush();
|
||||
if (self.show_imgui) {
|
||||
ImGUI.endFrame();
|
||||
}
|
||||
ImGUI.endFrame();
|
||||
Gfx.endFrame();
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user