//------------------------------------------------------------------------------ // offscreen.zig // // Render to an offscreen rendertarget texture, and use this texture // for rendering to the display. //------------------------------------------------------------------------------ const std = @import("std"); const stbi = @import("stbi"); const sokol = @import("sokol"); const slog = sokol.log; const sg = sokol.gfx; const sapp = sokol.app; const sglue = sokol.glue; const simgui = sokol.imgui; const sappimgui = sokol.appimgui; const ig = @import("cimgui_docking"); const vec3 = @import("math.zig").Vec3; const mat4 = @import("math.zig").Mat4; const shd = @import("shaders/main.glsl.zig"); const state = struct { var alloc: std.mem.Allocator = undefined; var io: std.Io = undefined; const Width = 800; const Height = 800; const offscreen = struct { var pass: sg.Pass = .{}; var pip: sg.Pipeline = .{}; var bind: sg.Bindings = .{}; var vs: shd.VsParams = .{ .position = .{ 0, 0 }, }; var fs: shd.FsParams = .{ .resolution = .{ Width, Height }, .rayCount = 16, .maxSteps = 4, .size = 1.0, }; var pixels: [state.Width * state.Height * 4]u8 = @splat(255); var pixels_updated: bool = true; var pixels_img: sg.Image = undefined; }; const display = struct { var pass_action: sg.PassAction = .{}; var pip: sg.Pipeline = .{}; var bind: sg.Bindings = .{}; }; }; export fn init() void { sg.setup(.{ .environment = sglue.environment(), .logger = .{ .func = slog.func }, }); simgui.setup(.{ .logger = .{ .func = slog.func }, }); sappimgui.setup(); ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable; state.offscreen.pass.action.colors[0] = .{ .load_action = .LOAD, .store_action = .STORE, }; state.offscreen.pass.action.depth = .{ .load_action = .LOAD, .store_action = .STORE, }; state.display.pass_action.colors[0] = .{ .load_action = .CLEAR, }; const offscreen_width = state.Width; const offscreen_height = state.Height; const offscreen_sample_count = 1; const color_img = sg.makeImage(.{ .usage = .{ .color_attachment = true }, .width = offscreen_width, .height = offscreen_height, .sample_count = offscreen_sample_count, .pixel_format = .RGBA8, }); state.offscreen.pixels_img = sg.makeImage(.{ .usage = .{ .dynamic_update = true }, .width = offscreen_width, .height = offscreen_height, .sample_count = offscreen_sample_count, .pixel_format = .RGBA8, }); const depth_img = sg.makeImage(.{ .usage = .{ .depth_stencil_attachment = true }, .width = offscreen_width, .height = offscreen_height, .sample_count = offscreen_sample_count, .pixel_format = .DEPTH, }); state.offscreen.pass.attachments.colors[0] = sg.makeView(.{ .color_attachment = .{ .image = color_img }, }); state.offscreen.pass.attachments.depth_stencil = sg.makeView(.{ .depth_stencil_attachment = .{ .image = depth_img }, }); state.offscreen.bind.views[shd.VIEW_off_tex] = sg.makeView(.{ .texture = .{ .image = state.offscreen.pixels_img }, }); state.display.bind.views[shd.VIEW_scr_tex] = sg.makeView(.{ .texture = .{ .image = color_img }, }); const vbuf = sg.makeBuffer(.{ .data = sg.asRange(&[6]f32{ -1.0, -1.0, -1.0, 3.0, 3.0, -1.0, }) }); state.offscreen.bind.vertex_buffers[0] = vbuf; state.display.bind.vertex_buffers[0] = vbuf; // shader and pipeline object for the default render pass state.offscreen.pip = sg.makePipeline(.{ .shader = sg.makeShader(shd.offscreenShaderDesc(sg.queryBackend())), .layout = init: { var l = sg.VertexLayoutState{}; l.attrs[shd.ATTR_offscreen_vpos] = .{ .format = .FLOAT2 }; break :init l; }, .cull_mode = .BACK, .sample_count = offscreen_sample_count, .depth = .{ .pixel_format = .DEPTH, .compare = .LESS_EQUAL, .write_enabled = true, }, .colors = init: { var c: [sg.max_color_attachments]sg.ColorTargetState = @splat(.{}); c[0].pixel_format = .RGBA8; break :init c; }, }); state.display.pip = sg.makePipeline(.{ .shader = sg.makeShader(shd.defaultShaderDesc(sg.queryBackend())), .layout = init: { var l = sg.VertexLayoutState{}; l.attrs[shd.ATTR_default_vpos] = .{ .format = .FLOAT2 }; break :init l; }, .cull_mode = .BACK, .depth = .{ .compare = .LESS_EQUAL, .write_enabled = true, }, }); // a sampler object for sampling the render target texture state.offscreen.bind.samplers[shd.SMP_off_smp] = sg.makeSampler(.{ .min_filter = .LINEAR, .mag_filter = .LINEAR, .wrap_u = .REPEAT, .wrap_v = .REPEAT, }); state.display.bind.samplers[shd.SMP_scr_smp] = sg.makeSampler(.{ .min_filter = .LINEAR, .mag_filter = .LINEAR, .wrap_u = .REPEAT, .wrap_v = .REPEAT, }); } export fn event(ev: [*c]const sapp.Event) void { sappimgui.trackEvent(ev.*); _ = simgui.handleEvent(ev.*); switch (ev.*.type) { .KEY_DOWN => { if (ev.*.key_code == .ESCAPE) { sapp.requestQuit(); } }, else => {}, } } // fn load_bmp(path: []const u8, buffer: []u8) !void { // var file = try std.Io.Dir.cwd().openFile(state.io, path, .{}); // var read_buffer: [1024]u8 = undefined; // var reader = file.reader(state.io, &read_buffer).interface; // var bfType = try reader.takeInt(u16, .little); // var bfSize = try reader.takeInt(u32, .little); // var bfReserved = try reader.takeInt(u32, .little); // var bfOffBits = try reader.takeInt(u32, .little); // var biSize = try reader.takeInt(u32, .little); // var biWidth = try reader.takeInt(u32, .little); // var biHeight = try reader.takeInt(u32, .little); // var biPlanes = try reader.takeInt(u16, .little); // var biBitCount = try reader.takeInt(u16, .little); // var biCompression = try reader.takeInt(u32, .little); // var biSizeImage = try reader.takeInt(u32, .little); // var biXPelsPerMeter = try reader.takeInt(u32, .little); // var biYPelsPerMeter = try reader.takeInt(u32, .little); // var biClrUsed = try reader.takeInt(u32, .little); // var biClrImportant = try reader.takeInt(u32, .little); // // if (buffer.len < biWidth * biHeight * biBitCount / 8) { // return error.BufferTooSmall; // } // // // } export fn frame() void { // call simgui.newFrame() before any ImGui calls sappimgui.trackFrame(); simgui.newFrame(.{ .width = sapp.width(), .height = sapp.height(), .delta_time = sapp.frameDuration(), .dpi_scale = sapp.dpiScale(), }); // const dt: f32 = @floatCast(sapp.frameDuration() * 60); // const aspect = sapp.widthf() / sapp.heightf(); state.offscreen.fs.resolution[0] = sapp.widthf(); state.offscreen.fs.resolution[1] = sapp.heightf(); if (ig.igBegin("abc", null, 0)) { // vs _ = ig.igDragFloat2("position", &state.offscreen.vs.position); // fs _ = ig.igInputInt("rayCount", &state.offscreen.fs.rayCount); _ = ig.igInputInt("maxSteps", &state.offscreen.fs.maxSteps); _ = ig.igDragFloatEx("size", &state.offscreen.fs.size, 0.1, 0, 100, "%f", 0); if (ig.igButton("load png")) { var x: c_int = 0; var y: c_int = 0; var n: c_int = 0; const img = stbi.stbi_load("test.png", &x, &y, &n, 0); @memcpy(&state.offscreen.pixels, img); stbi.stbi_image_free(img); state.offscreen.pixels_updated = true; } } ig.igEnd(); if (state.offscreen.pixels_updated) { state.offscreen.pixels_updated = false; var imageData: sg.ImageData = .{}; imageData.mip_levels[0] = sg.asRange(&state.offscreen.pixels); sg.updateImage(state.offscreen.pixels_img, imageData); } sg.beginPass(state.offscreen.pass); sg.applyPipeline(state.offscreen.pip); sg.applyBindings(state.offscreen.bind); sg.applyUniforms(shd.UB_vs_params, sg.asRange(&state.offscreen.vs)); sg.applyUniforms(shd.UB_fs_params, sg.asRange(&state.offscreen.fs)); sg.draw(0, 3, 1); sg.endPass(); sg.beginPass(.{ .action = state.display.pass_action, .swapchain = sglue.swapchain() }); sg.applyPipeline(state.display.pip); sg.applyBindings(state.display.bind); sg.draw(0, 3, 1); simgui.render(); sg.endPass(); sg.commit(); } export fn cleanup() void { sappimgui.shutdown(); simgui.shutdown(); sg.shutdown(); } pub fn main(_init: std.process.Init) void { state.alloc = _init.arena.allocator(); state.io = _init.io; sapp.run(.{ .init_cb = init, .frame_cb = frame, .event_cb = event, .cleanup_cb = cleanup, .sample_count = 4, .width = state.Width, .height = state.Height, .icon = .{ .sokol_default = true }, .window_title = "offscreen.zig", .logger = .{ .func = slog.func }, }); }