1 //------------------------------------------------------------------------------
2 //  sgl-context.d
3 //
4 //  Demonstrates how to render into different render passes with sokol-gl.
5 //  using contexts.
6 //------------------------------------------------------------------------------
7 module examples.sgl_context;
8 
9 private:
10 
11 import sg = sokol.gfx;
12 import sglue = sokol.glue;
13 import sapp = sokol.app;
14 import slog = sokol.log;
15 import sgl = sokol.gl;
16 import handmade.math : sin, cos;
17 
18 extern (C):
19 @safe:
20 
21 struct Offscreen
22 {
23     sgl.Context sgl_ctx;
24     sg.Pass pass = {
25         action: {
26             colors: [
27                 {
28                     load_action: sg.LoadAction.Clear, clear_value: {
29                         r: 0, g: 0, b: 0, a: 1
30                     }
31                 },
32             ],
33         },
34     };
35 }
36 
37 struct Display
38 {
39     sg.Sampler smp;
40     sg.View tex_view;
41     sgl.Pipeline sgl_pip;
42     sg.PassAction pass_action = {
43         colors: [
44             {
45                 load_action: sg.LoadAction.Clear, clear_value: {
46                     r: 0.5, g: 0.7, b: 1.0, a: 1.0
47                 }
48             },
49         ],
50     };
51 }
52 
53 struct State
54 {
55     Display display;
56     Offscreen offscreen;
57 }
58 
59 static State state;
60 
61 enum offscreen_pixel_format = sg.PixelFormat.Rgba8;
62 enum offscreen_sample_count = 1;
63 enum offscreen_width = 32;
64 enum offscreen_height = 32;
65 
66 void init()
67 {
68     sg.Desc gfxd = {
69         environment: sglue.environment(),
70         logger: {func: &slog.func}
71     };
72     sg.setup(gfxd);
73 
74     // setup sokol-gl with the default context compatible with the default
75     // render pass (which means just keep pixelformats and sample count at defaults)
76     //
77     // reduce the vertex- and command-count though, otherwise we just waste memory
78     sgl.Desc gld = {
79         max_vertices: 64,
80         max_commands: 16,
81         logger: {func: &slog.func},
82     };
83     sgl.setup(gld);
84 
85     // create a sokol-gl pipeline object for 3D rendering into the default pass
86     sg.PipelineDesc pld = {
87         cull_mode: sg.CullMode.Back,
88         depth: {write_enabled: true,
89         compare: sg.CompareFunc.Less_equal},
90     };
91     state.display.sgl_pip = sgl.contextMakePipeline(sgl.defaultContext, pld);
92 
93     // create a sokol-gl context compatible with the offscreen render pass
94     // (specific color pixel format, no depth-stencil-surface, no MSAA)
95     sgl.ContextDesc ctd = {
96         max_vertices: 8,
97         max_commands: 4,
98         color_format: offscreen_pixel_format,
99         depth_format: sg.PixelFormat.None,
100         sample_count: offscreen_sample_count
101     };
102     state.offscreen.sgl_ctx = sgl.makeContext(ctd);
103 
104     // setup offscreen render pass attachment image and view objects
105     sg.ImageDesc img_desc = {
106         usage: { color_attachment: true },
107         width: offscreen_width,
108         height: offscreen_height,
109         pixel_format: offscreen_pixel_format,
110         sample_count: offscreen_sample_count
111     };
112     auto img = sg.makeImage(img_desc);
113     sg.ViewDesc att_view_desc = { color_attachment: { image: img } };
114     state.offscreen.pass.attachments.colors[0] = sg.makeView(att_view_desc);
115     sg.ViewDesc tex_view_desc = { texture: { image: img } };
116     state.display.tex_view = sg.makeView(tex_view_desc);
117 
118     // sampler for sampling the offscreen render target
119     sg.SamplerDesc smp_desc = {
120         wrap_u: sg.Wrap.Clamp_to_edge,
121         wrap_v: sg.Wrap.Clamp_to_edge,
122         min_filter: sg.Filter.Nearest,
123         mag_filter: sg.Filter.Nearest
124     };
125     state.display.smp = sg.makeSampler(smp_desc);
126 }
127 
128 void frame()
129 {
130     immutable float a = sgl.asRadians(sapp.frameCount());
131 
132     // draw a rotating quad into the offscreen render target texture
133     sgl.setContext(state.offscreen.sgl_ctx);
134     sgl.defaults();
135     sgl.matrixModeModelview();
136     sgl.rotate(a, 0.0, 0.0, 1.0);
137     draw_quad();
138 
139     // draw a rotating 3D cube, using the offscreen render target as texture
140     sgl.setContext(sgl.defaultContext());
141     sgl.defaults();
142     sgl.enableTexture();
143     sgl.texture(state.display.tex_view, state.display.smp);
144     sgl.loadPipeline(state.display.sgl_pip);
145     sgl.matrixModeProjection();
146     sgl.perspective(sgl.asRadians(45.0), sapp.widthf() / sapp.heightf(), 0.1, 100.0);
147     immutable(float)[3] eye = [sin(a) * 6.0, sin(a) * 3.0, cos(a) * 6.0];
148     sgl.matrixModeModelview();
149     sgl.lookat(eye[0], eye[1], eye[2], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
150     draw_cube();
151 
152     // do the actual offscreen and display rendering in sokol-gfx passes
153     sg.beginPass(state.offscreen.pass);
154     sgl.contextDraw(state.offscreen.sgl_ctx);
155     sg.endPass();
156     sg.Pass display_pass = {
157         action: state.display.pass_action,
158         swapchain: sglue.swapchain
159     };
160     sg.beginPass(display_pass);
161     sgl.contextDraw(sgl.defaultContext());
162     sg.endPass();
163     sg.commit();
164 }
165 
166 void cleanup()
167 {
168     sgl.shutdown();
169     sg.shutdown();
170 }
171 
172 void main()
173 {
174     sapp.Desc runner = {
175         window_title: "sgl-context.d",
176         init_cb: &init,
177         frame_cb: &frame,
178         cleanup_cb: &cleanup,
179         width: 800,
180         height: 600,
181         sample_count: 4,
182         logger: {func: &slog.func},
183         icon: {sokol_default: true}
184     };
185     sapp.run(runner);
186 }
187 
188 void draw_quad()
189 {
190     sgl.beginQuads();
191     sgl.v2fC3b(0.0, -1.0, 255, 0, 0);
192     sgl.v2fC3b(1.0, 0.0, 0, 0, 255);
193     sgl.v2fC3b(0.0, 1.0, 0, 255, 255);
194     sgl.v2fC3b(-1.0, 0.0, 0, 255, 0);
195     sgl.end();
196 }
197 
198 void draw_cube()
199 {
200     sgl.beginQuads();
201     sgl.v3fT2f(-1.0, 1.0, -1.0, 0.0, 1.0);
202     sgl.v3fT2f(1.0, 1.0, -1.0, 1.0, 1.0);
203     sgl.v3fT2f(1.0, -1.0, -1.0, 1.0, 0.0);
204     sgl.v3fT2f(-1.0, -1.0, -1.0, 0.0, 0.0);
205     sgl.v3fT2f(-1.0, -1.0, 1.0, 0.0, 1.0);
206     sgl.v3fT2f(1.0, -1.0, 1.0, 1.0, 1.0);
207     sgl.v3fT2f(1.0, 1.0, 1.0, 1.0, 0.0);
208     sgl.v3fT2f(-1.0, 1.0, 1.0, 0.0, 0.0);
209     sgl.v3fT2f(-1.0, -1.0, 1.0, 0.0, 1.0);
210     sgl.v3fT2f(-1.0, 1.0, 1.0, 1.0, 1.0);
211     sgl.v3fT2f(-1.0, 1.0, -1.0, 1.0, 0.0);
212     sgl.v3fT2f(-1.0, -1.0, -1.0, 0.0, 0.0);
213     sgl.v3fT2f(1.0, -1.0, 1.0, 0.0, 1.0);
214     sgl.v3fT2f(1.0, -1.0, -1.0, 1.0, 1.0);
215     sgl.v3fT2f(1.0, 1.0, -1.0, 1.0, 0.0);
216     sgl.v3fT2f(1.0, 1.0, 1.0, 0.0, 0.0);
217     sgl.v3fT2f(1.0, -1.0, -1.0, 0.0, 1.0);
218     sgl.v3fT2f(1.0, -1.0, 1.0, 1.0, 1.0);
219     sgl.v3fT2f(-1.0, -1.0, 1.0, 1.0, 0.0);
220     sgl.v3fT2f(-1.0, -1.0, -1.0, 0.0, 0.0);
221     sgl.v3fT2f(-1.0, 1.0, -1.0, 0.0, 1.0);
222     sgl.v3fT2f(-1.0, 1.0, 1.0, 1.0, 1.0);
223     sgl.v3fT2f(1.0, 1.0, 1.0, 1.0, 0.0);
224     sgl.v3fT2f(1.0, 1.0, -1.0, 0.0, 0.0);
225     sgl.end();
226 }
227 
228 version (WebAssembly)
229 {
230     debug
231     {
232         import emscripten.assertd;
233     }
234 }