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 }