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