1 //------------------------------------------------------------------------------ 2 // offscreen.d 3 // 4 // Render to an offscreen rendertarget texture, and use this texture 5 // for rendering to the display. 6 //------------------------------------------------------------------------------ 7 module examples.offscreen; 8 9 private: 10 11 import sg = sokol.gfx; 12 import app = sokol.app; 13 import log = sokol.log; 14 import handmade.math : Mat4, Vec3; 15 import sglue = sokol.glue; 16 import sshape = sokol.shape; 17 import shd = shaders.offscreen; 18 19 extern (C): 20 @safe: 21 22 struct State 23 { 24 float rx = 0; 25 float ry = 0; 26 struct Offscreen 27 { 28 sg.Pipeline pip; 29 sg.Bindings bind; 30 sg.Pass pass; 31 } 32 33 Offscreen offscreen; 34 35 struct Display 36 { 37 sg.Pipeline pip; 38 sg.Bindings bind; 39 sg.PassAction pass_action; 40 } 41 42 Display display; 43 44 sshape.ElementRange sphere, donut; 45 } 46 47 static State state; 48 49 void init() 50 { 51 sg.Desc gfxd = {environment: sglue.environment, 52 logger: {func: &log.func}}; 53 sg.setup(gfxd); 54 55 // default pass action: clear to blue-ish 56 state.display.pass_action.colors[0].load_action = sg.LoadAction.Clear; 57 state.display.pass_action.colors[0].clear_value.r = 0.25; 58 state.display.pass_action.colors[0].clear_value.g = 0.45; 59 state.display.pass_action.colors[0].clear_value.b = 0.65; 60 state.display.pass_action.colors[0].clear_value.a = 1.0; 61 // offscreen pass action: clear to black 62 state.offscreen.pass.action.colors[0].load_action = sg.LoadAction.Clear; 63 state.offscreen.pass.action.colors[0].clear_value.r = 0.25; 64 state.offscreen.pass.action.colors[0].clear_value.g = 0.25; 65 state.offscreen.pass.action.colors[0].clear_value.b = 0.25; 66 state.offscreen.pass.action.colors[0].clear_value.a = 1.0; 67 68 // setup the color- and depth-stencil-attachment images and views 69 int offscreen_width = 256; 70 int offscreen_height = 256; 71 int offscreen_sample_count = 1; 72 sg.ImageDesc color_img_desc = { 73 usage: { color_attachment: true }, 74 width: offscreen_width, 75 height: offscreen_height, 76 pixel_format: sg.PixelFormat.Rgba8, 77 sample_count: offscreen_sample_count, 78 }; 79 const color_img = sg.makeImage(color_img_desc); 80 sg.ImageDesc depth_img_desc = { 81 usage: { depth_stencil_attachment: true }, 82 width: offscreen_width, 83 height: offscreen_height, 84 pixel_format: sg.PixelFormat.Depth, 85 sample_count: offscreen_sample_count, 86 }; 87 const depth_img = sg.makeImage(depth_img_desc); 88 89 // the offscreen render pass needs a color- and depth-stencil-attachment view 90 sg.ViewDesc color_attview_desc = { 91 color_attachment: { image: color_img }, 92 }; 93 state.offscreen.pass.attachments.colors[0] = sg.makeView(color_attview_desc); 94 sg.ViewDesc depth_attview_desc = { 95 depth_stencil_attachment: { image: depth_img }, 96 }; 97 state.offscreen.pass.attachments.depth_stencil = sg.makeView(depth_attview_desc); 98 99 // the display render pass needs a texture view on the color image 100 sg.ViewDesc color_texview_desc = { 101 texture: { image: color_img }, 102 }; 103 state.display.bind.views[shd.VIEW_TEX] = sg.makeView(color_texview_desc); 104 105 // a donut shape which is rendered into the offscreen render target, and 106 // a sphere shape which is rendered into the default framebuffer 107 sshape.Vertex[4000] vertices; 108 ushort[24_000] indices; 109 // dfmt off 110 sshape.Buffer buf = { 111 vertices: {buffer: sshape.Range(&vertices, vertices.sizeof) }, 112 indices: { buffer: sshape.Range(&indices, indices.sizeof) }, 113 }; 114 buf = sshape.buildTorus(buf, sshape.Torus( 115 radius: 0.5, 116 ring_radius: 0.3, 117 sides: 20, 118 rings: 36, 119 )); 120 // dfmt on 121 state.donut = sshape.elementRange(buf); 122 // dfmt off 123 buf = sshape.buildSphere(buf, sshape.Sphere( 124 radius: 0.5, 125 slices: 72, 126 stacks: 40, 127 )); 128 // dfmt on 129 state.sphere = sshape.elementRange(buf); 130 131 const vbuf = sg.makeBuffer(sshape.vertexBufferDesc(buf)); 132 state.offscreen.bind.vertex_buffers[0] = vbuf; 133 state.display.bind.vertex_buffers[0] = vbuf; 134 const ibuf = sg.makeBuffer(sshape.indexBufferDesc(buf)); 135 state.offscreen.bind.index_buffer = ibuf; 136 state.display.bind.index_buffer = ibuf; 137 138 // dfmt off 139 // shader and pipeline object for offscreen rendering 140 sg.PipelineDesc offscreen_pip_desc = { 141 layout: { 142 attrs: [ 143 shd.ATTR_OFFSCREEN_POSITION: sshape.positionVertexAttrState, 144 shd.ATTR_OFFSCREEN_NORMAL: sshape.normalVertexAttrState, 145 ], 146 buffers: [sshape.vertexBufferLayoutState], 147 }, 148 colors: [{pixel_format: sg.PixelFormat.Rgba8}], 149 shader: sg.makeShader(shd.offscreenShaderDesc(sg.queryBackend)), 150 index_type: sg.IndexType.Uint16, 151 cull_mode: sg.CullMode.Back, 152 sample_count: 1, 153 depth: { 154 pixel_format: sg.PixelFormat.Depth, 155 write_enabled: true, 156 compare: sg.CompareFunc.Less_equal 157 }, 158 }; 159 sg.PipelineDesc default_pip_desc = { 160 layout: { 161 attrs: [ 162 shd.ATTR_DEFAULT_POSITION: sshape.positionVertexAttrState, 163 shd.ATTR_DEFAULT_NORMAL: sshape.normalVertexAttrState, 164 shd.ATTR_DEFAULT_TEXCOORD0: sshape.texcoordVertexAttrState, 165 ], 166 buffers: [sshape.vertexBufferLayoutState], 167 }, 168 shader: sg.makeShader(shd.defaultShaderDesc(sg.queryBackend)), 169 index_type: sg.IndexType.Uint16, 170 cull_mode: sg.CullMode.Back, 171 depth: { 172 write_enabled: true, 173 compare: sg.CompareFunc.Less_equal 174 }, 175 }; 176 // dfmt on 177 state.offscreen.pip = sg.makePipeline(offscreen_pip_desc); 178 state.display.pip = sg.makePipeline(default_pip_desc); 179 180 // dfmt off 181 // a sampler object for sampling the render target texture 182 state.display.bind.samplers[shd.SMP_SMP] = sg.makeSampler ( 183 sg.SamplerDesc( 184 min_filter: sg.Filter.Linear, 185 mag_filter: sg.Filter.Linear, 186 wrap_u: sg.Wrap.Repeat, 187 wrap_v: sg.Wrap.Repeat) 188 ); 189 // dfmt on 190 } 191 192 void frame() 193 { 194 immutable float t = cast(float)(app.frameDuration() * 60.0); 195 immutable float aspect = app.widthf / app.heightf; 196 197 state.rx += 1.0 * t; 198 state.ry += 2.0 * t; 199 200 shd.VsParams offscreenVsParams = computeMvp(state.rx, state.ry, 1.0, 2.5); 201 shd.VsParams defaultVsParams = computeMvp(-state.rx * 0.25, state.ry * 0.25, aspect, 2); 202 203 sg.beginPass(state.offscreen.pass); 204 sg.applyPipeline(state.offscreen.pip); 205 sg.applyBindings(state.offscreen.bind); 206 sg.applyUniforms(shd.UB_VS_PARAMS, sg.Range(&offscreenVsParams, offscreenVsParams.sizeof)); 207 sg.draw(state.donut.base_element, state.donut.num_elements, 1); 208 sg.endPass(); 209 210 // dfmt off 211 sg.beginPass(sg.Pass( 212 action: state.display.pass_action, 213 swapchain: sglue.swapchain 214 )); 215 // dfmt on 216 sg.applyPipeline(state.display.pip); 217 sg.applyBindings(state.display.bind); 218 sg.applyUniforms(shd.UB_VS_PARAMS, sg.Range(&defaultVsParams, defaultVsParams.sizeof)); 219 sg.draw(state.sphere.base_element, state.sphere.num_elements, 1); 220 sg.endPass(); 221 222 sg.commit(); 223 } 224 225 void cleanup() 226 { 227 sg.shutdown(); 228 } 229 230 shd.VsParams computeMvp(float rx, float ry, float aspect, float eye_dist) 231 { 232 immutable proj = Mat4.perspective(60.0, aspect, 0.01, 10.0); 233 immutable view = Mat4.lookAt(Vec3(0.0, 0.0, eye_dist), Vec3.zero, Vec3.up); 234 immutable rxm = Mat4.rotate(rx, Vec3(1.0, 0.0, 0.0)); 235 immutable rym = Mat4.rotate(ry, Vec3(0.0, 1.0, 0.0)); 236 immutable model = Mat4.mul(rxm, rym); 237 immutable view_proj = Mat4.mul(proj, view); 238 return shd.VsParams(mvp : Mat4.mul(view_proj, model)); 239 } 240 241 void main() 242 { 243 // dfmt off 244 app.Desc runner = { 245 window_title: "offscreen.d", 246 init_cb: &init, 247 frame_cb: &frame, 248 cleanup_cb: &cleanup, 249 width: 800, 250 height: 600, 251 sample_count: 4, 252 icon: {sokol_default: true}, 253 logger: {func: &log.func} 254 }; 255 app.run(runner); 256 // dfmt on 257 } 258 259 version (WebAssembly) 260 { 261 debug 262 { 263 import emscripten.assertd; 264 } 265 }