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 }