Graphics library for Mercury, including OpenGL bindings, TGA image reading, and X11, Win32, and SDL2 windowing and input.
リビジョン | bde46bead99fec6dfa3bb1bb1d2a1186c2487341 (tree) |
---|---|
日時 | 2023-06-27 04:12:19 |
作者 | AlaskanEmily <emily@alas...> |
コミッター | AlaskanEmily |
Add GLFW delegate window backend.
This doesn't yet have a Java implementation, but providing one with LWJGL is
the next step for a usable Java backend.
@@ -0,0 +1,310 @@ | ||
1 | +% Released as public domain by Transnat Games for demonstration purposes. | |
2 | +% | |
3 | +% Any copyright is dedicated to the Public Domain. | |
4 | +% https://creativecommons.org/publicdomain/zero/1.0/ | |
5 | + | |
6 | +:- module saffron_glfw_demo. | |
7 | + | |
8 | +%=============================================================================% | |
9 | +:- interface. | |
10 | +%=============================================================================% | |
11 | + | |
12 | +:- use_module io. | |
13 | + | |
14 | +%-----------------------------------------------------------------------------% | |
15 | + | |
16 | +:- pred main(io.io::di, io.io::uo) is det. | |
17 | + | |
18 | +%=============================================================================% | |
19 | +:- implementation. | |
20 | +%=============================================================================% | |
21 | + | |
22 | +:- use_module bool. | |
23 | +:- use_module exception. | |
24 | +:- import_module float. | |
25 | +:- import_module int. | |
26 | +:- import_module list. | |
27 | +:- use_module math. | |
28 | +:- use_module maybe. | |
29 | +:- use_module thread. | |
30 | +:- use_module thread.mvar. | |
31 | + | |
32 | +:- use_module mmath. | |
33 | +:- use_module mmath.matrix. | |
34 | +:- use_module mmath.vector. | |
35 | +:- import_module mmath.vector.vector3. | |
36 | +:- import_module mmath.vector.vector4. | |
37 | + | |
38 | +:- use_module saffron. | |
39 | +:- use_module saffron.draw. | |
40 | +:- import_module saffron.geometry. | |
41 | +:- use_module saffron.gl. | |
42 | +:- use_module saffron.gl2. | |
43 | +:- use_module saffron.soft. | |
44 | +:- use_module saffron_window. | |
45 | +:- use_module saffron_delegate. | |
46 | +:- use_module saffron_glfw. | |
47 | +:- use_module saffron_tga. | |
48 | + | |
49 | +:- use_module timer. | |
50 | + | |
51 | +%-----------------------------------------------------------------------------% | |
52 | + | |
53 | +:- type state(Ctx, Group) ---> state(thread.mvar.mvar({Ctx, Group})). | |
54 | + | |
55 | +%-----------------------------------------------------------------------------% | |
56 | + | |
57 | +:- pred redraw(state(Ctx, Group), io.io, io.io) | |
58 | + <= (saffron.gl.context(Ctx), | |
59 | + saffron.draw.basic_transform(Ctx), | |
60 | + saffron.draw.transform_stack(Ctx), | |
61 | + saffron.draw.draw(Ctx, Group)). | |
62 | +:- mode redraw(in, di, uo) is det. | |
63 | + | |
64 | +redraw(state(MVar), !IO) :- | |
65 | + thread.mvar.read(MVar, {Ctx, Group}, !IO), | |
66 | + % Get the current time in order to animate the camera. | |
67 | + timer.ms(Ticks, !IO), | |
68 | + Seconds = float(int.unchecked_rem(Ticks, 6283185)) / 1000.0, | |
69 | + | |
70 | + % Alternatively... | |
71 | + % time.clock(Ticks, !IO), | |
72 | + % (time.clocks_per_sec =< 0 -> Rate = 100 ; Rate = time.clocks_per_sec), | |
73 | + % Seconds = float(Ticks) / float(Rate), | |
74 | + | |
75 | + % Build the rotation matrix. | |
76 | + % To make this more visually interesting, we also move the rotation | |
77 | + % vector. If this seems confusing, just change RotateVector to be | |
78 | + % `vector(U1, U1, 1.0)` or some other constant to see the rotation | |
79 | + % normal in action. | |
80 | + SpinT = Seconds, % / 0.23, | |
81 | + Angle = Seconds * 0.87, | |
82 | + RotateVector = | |
83 | + mmath.vector.normalize(vector(math.sin(SpinT), 1.0, math.cos(SpinT))), | |
84 | + % In general, you should always normalize vectors being used to build | |
85 | + % transformation matrices. | |
86 | + Matrix = mmath.matrix.rotate(Angle, RotateVector), | |
87 | + | |
88 | + % Here we will push the ortho matrix set in main/2 and then transform | |
89 | + % it by our rotation. Then, after drawing, we pop the transformed | |
90 | + % matrix to restore the ortho matrix. | |
91 | + saffron.draw.push_matrix(Ctx, !IO), | |
92 | + | |
93 | + % In most actual programs, this would be tracked separately for the | |
94 | + % model and camera. | |
95 | + saffron.draw.transform(Ctx, Matrix, !IO), | |
96 | + | |
97 | + % Draw the model. Most real programs would iterate a set of Groups. | |
98 | + saffron.draw.draw(Ctx, Group, !IO), | |
99 | + | |
100 | + saffron.draw.pop_matrix(Ctx, !IO). | |
101 | + | |
102 | +%-----------------------------------------------------------------------------% | |
103 | + | |
104 | +:- instance saffron_delegate.delegate(state(Ctx, Group)) | |
105 | + <= (saffron.gl.context(Ctx), | |
106 | + saffron.draw.basic_transform(Ctx), | |
107 | + saffron.draw.transform_stack(Ctx), | |
108 | + saffron.draw.draw(Ctx, Group)) where [ | |
109 | + pred(saffron_delegate.key_press/4) is saffron_delegate.key_nop, | |
110 | + pred(saffron_delegate.key_release/4) is saffron_delegate.key_nop, | |
111 | + pred(saffron_delegate.button_press/6) is saffron_delegate.button_nop, | |
112 | + pred(saffron_delegate.button_release/6) is saffron_delegate.button_nop, | |
113 | + pred(saffron_delegate.mouse_motion/5) is saffron_delegate.motion_nop, | |
114 | + pred(saffron_delegate.resize/5) is saffron_delegate.resize_nop, | |
115 | + pred(saffron_delegate.redraw/3) is saffron_glfw_demo.redraw, | |
116 | + pred(saffron_delegate.quit/3) is saffron_delegate.quit_nop | |
117 | +]. | |
118 | + | |
119 | +%-----------------------------------------------------------------------------% | |
120 | + | |
121 | +:- pred run(Win, thread.mvar.mvar({T1, T2}), io.io, io.io) | |
122 | + <= saffron_delegate.window(Win). | |
123 | +:- mode run(in, in, di, uo) is det. | |
124 | + | |
125 | +run(Win, MVar, !IO) :- | |
126 | + % Although this isn't really needed here, it is good hygiene to ensure that | |
127 | + % you measure your game and update calls, and then sleep based on the | |
128 | + % difference of the time used and expected frame time. | |
129 | + timer.ms(NewTicks, !IO), | |
130 | + | |
131 | + saffron_delegate.update(Win, ShouldQuit, !IO), | |
132 | + ( | |
133 | + ShouldQuit = bool.yes % Nothing else to do. | |
134 | + ; | |
135 | + ShouldQuit = bool.no, | |
136 | + % Delay, then continue looping. | |
137 | + % The delay is from now (EndMS) until the anticipated end of frame/tick | |
138 | + % (NewTicks + 16). This should give us an optimistic 60 FPS. | |
139 | + timer.ms(EndMS, !IO), | |
140 | + SleepMS = NewTicks + 16 - EndMS, | |
141 | + % Delay only if we didn't blow the frame budget. | |
142 | + ( SleepMS >= 0 -> timer.sleep(SleepMS, !IO) ; true ), | |
143 | + run(Win, MVar, !IO) | |
144 | + ). | |
145 | + | |
146 | +%-----------------------------------------------------------------------------% | |
147 | +% Geometry for the cube model. | |
148 | +% | |
149 | +% Normally, you will want to load a model file, or perhaps just have a few | |
150 | +% primitives defined in a similar to this. | |
151 | +% | |
152 | +% 0 ----- 1 | |
153 | +% \ \ | |
154 | +% | \ | \ | |
155 | +% | 2 ----- 3 | |
156 | +% | | | | | |
157 | +% | | | | | |
158 | +% 4 -|--- 5 | | |
159 | +% \ | \ | | |
160 | +% \ \ | |
161 | +% 6 ----- 7 | |
162 | +% | |
163 | +% y | |
164 | +% z | | |
165 | +% \ | | |
166 | +% \| | |
167 | +% o----x | |
168 | +% | |
169 | +% Faces (in triangle strip order): | |
170 | +% 0: 0, 1, 2, 3 | |
171 | +% 1: 0, 2, 4, 6 | |
172 | +% 2: 3, 1, 7, 5 | |
173 | +% 3: 1, 0, 5, 4 | |
174 | +% 4: 2, 3, 6, 7 | |
175 | +% 5: 6, 7, 4, 5 | |
176 | +:- func cube_geometry = list.list(list.list(saffron.geometry.vertex3d)). | |
177 | +cube_geometry = [ | |
178 | + [ | |
179 | + vertex(Vec0, U0, U0) |[ | |
180 | + vertex(Vec1, U1, U0) |[ | |
181 | + vertex(Vec2, U0, U1) |[ | |
182 | + vertex(Vec3, U1, U1) |[ | |
183 | + ]]]]] |[ | |
184 | + [ | |
185 | + vertex(Vec0, U0, U0) |[ | |
186 | + vertex(Vec2, U1, U0) |[ | |
187 | + vertex(Vec4, U0, U1) |[ | |
188 | + vertex(Vec6, U1, U1) |[ | |
189 | + ]]]]] |[ | |
190 | + [ | |
191 | + vertex(Vec3, U0, U0) |[ | |
192 | + vertex(Vec1, U1, U0) |[ | |
193 | + vertex(Vec7, U0, U1) |[ | |
194 | + vertex(Vec5, U1, U1) |[ | |
195 | + ]]]]] |[ | |
196 | + [ | |
197 | + vertex(Vec1, U0, U0) |[ | |
198 | + vertex(Vec0, U1, U0) |[ | |
199 | + vertex(Vec5, U0, U1) |[ | |
200 | + vertex(Vec4, U1, U1) |[ | |
201 | + ]]]]] |[ | |
202 | + [ | |
203 | + vertex(Vec2, U0, U0) |[ | |
204 | + vertex(Vec3, U1, U0) |[ | |
205 | + vertex(Vec6, U0, U1) |[ | |
206 | + vertex(Vec7, U1, U1) |[ | |
207 | + ]]]]] |[ | |
208 | + [ | |
209 | + vertex(Vec6, U0, U0) |[ | |
210 | + vertex(Vec7, U1, U0) |[ | |
211 | + vertex(Vec4, U0, U1) |[ | |
212 | + vertex(Vec5, U1, U1) |[ | |
213 | + ]]]]] |[ | |
214 | + ]]]]]]] :- | |
215 | + | |
216 | + Vec0 = vector(-1.0, 1.0, 1.0), | |
217 | + Vec1 = vector( 1.0, 1.0, 1.0), | |
218 | + Vec2 = vector(-1.0, 1.0, -1.0), | |
219 | + Vec3 = vector( 1.0, 1.0, -1.0), | |
220 | + Vec4 = vector(-1.0, -1.0, 1.0), | |
221 | + Vec5 = vector( 1.0, -1.0, 1.0), | |
222 | + Vec6 = vector(-1.0, -1.0, -1.0), | |
223 | + Vec7 = vector( 1.0, -1.0, -1.0), | |
224 | + U0 = 0.0, | |
225 | + U1 = 1.0 . | |
226 | + | |
227 | +%-----------------------------------------------------------------------------% | |
228 | + | |
229 | +main(!IO) :- | |
230 | + % Load the demo image | |
231 | + saffron_tga.load_path("res/crate.tga", ImageResult, !IO), | |
232 | + | |
233 | + % The Mercury compiler is smart enough to see that the error branch will | |
234 | + % throw, and will unify the result outside that disjunction. | |
235 | + ( | |
236 | + ImageResult = io.error(Err), | |
237 | + exception.throw(exception.software_error(io.error_message(Err))) | |
238 | + ; | |
239 | + ImageResult = io.ok({BMP, TexW, TexH}) | |
240 | + ), | |
241 | + W = 640, | |
242 | + H = 480, | |
243 | + Title = "Saffron GLFW Demo", | |
244 | + | |
245 | + thread.mvar.init(MVar, !IO), | |
246 | + saffron_glfw.create_window(state(MVar), W, H, Title, 2, 0, WinRes, !IO), | |
247 | + ( | |
248 | + WinRes = io.error(Err), | |
249 | + exception.throw(exception.software_error(io.error_message(Err))) | |
250 | + ; | |
251 | + WinRes = io.ok(Win) | |
252 | + ), | |
253 | + | |
254 | + % Using the backend-specific type is what determines what backend Glow is | |
255 | + % using. | |
256 | + saffron.gl2.init(Win, Ctx, !IO), | |
257 | + | |
258 | + saffron.gl.opengl_version_string(Ctx, GLVersion, !IO), | |
259 | + io.write_string("OpenGL Version: ", !IO), | |
260 | + io.write_string(GLVersion, !IO), | |
261 | + io.nl(!IO), | |
262 | + | |
263 | + saffron.gl.shader_model_version_string(Ctx, GLSLVersion, !IO), | |
264 | + io.write_string("GLSL Version: ", !IO), | |
265 | + io.write_string(GLSLVersion, !IO), | |
266 | + io.nl(!IO), | |
267 | + | |
268 | + % Set the ortho mode | |
269 | + AR = float(W) / float(H), | |
270 | + | |
271 | + Matrix = mmath.matrix.ortho(-AR, AR, 1.0, -1.0, -1000.0, 1000.0), | |
272 | + saffron.draw.set_matrix(Ctx, Matrix, !IO), | |
273 | + | |
274 | + saffron.draw.transform(Ctx, mmath.matrix.scale(0.4, 0.4, 0.4), !IO), | |
275 | + | |
276 | + % Upload the image to create a texture. | |
277 | + saffron.gl.create_texture_from_bitmap(Ctx, BMP, TexW, TexH, Texture, !IO), | |
278 | + Faces = cube_geometry, | |
279 | + | |
280 | + % Upload geometry to buffers. | |
281 | + % Most programs would probably want to use index buffers for a shape like | |
282 | + % this with mostly shared geometry. | |
283 | + list.map_foldl( | |
284 | + saffron.geometry.create_buffer_list(Ctx), | |
285 | + Faces, FaceBuffers, | |
286 | + !IO), | |
287 | + | |
288 | + % Construct the faces. | |
289 | + % This *could* be done as one large, wound shape, but the majority of | |
290 | + % programs will be loading meshes that will contain individual hulls/faces | |
291 | + % that will need to be constructed in this way. | |
292 | + list.map_foldl( | |
293 | + saffron.geometry.create_shape2(Ctx, saffron.geometry.triangle_strip, Texture), | |
294 | + FaceBuffers, | |
295 | + Shapes, | |
296 | + !IO), | |
297 | + | |
298 | + % Construct the shape. | |
299 | + saffron.draw.create_group_list(Ctx, Shapes, Group, !IO), | |
300 | + | |
301 | + thread.mvar.put(MVar, {Ctx, Group}, !IO), | |
302 | + % Run the engine. | |
303 | + run(Win, MVar, !IO), | |
304 | + | |
305 | + % Cleanup | |
306 | + saffron.destroy(Ctx, Group, !IO), | |
307 | + list.foldl(saffron.destroy(Ctx), Shapes, !IO), | |
308 | + list.foldl(saffron.destroy(Ctx), FaceBuffers, !IO), | |
309 | + saffron.destroy(Ctx, Texture, !IO). | |
310 | + |
@@ -0,0 +1,2 @@ | ||
1 | +cd `dirname "$0"` | |
2 | +exec sh run_demo.sh saffron_glfw_demo $@ |
@@ -23,7 +23,7 @@ all: $(ALL) | ||
23 | 23 | UTILS=build_saffron_window build_saffron_delegate build_saffron_glow build_saffron_tga |
24 | 24 | utils: $(UTILS) |
25 | 25 | |
26 | -DEMOS=build_saffron_cube_demo build_saffron_shader_demo | |
26 | +DEMOS=build_saffron_cube_demo build_saffron_shader_demo build_saffron_glfw_demo | |
27 | 27 | demos: $(DEMOS) |
28 | 28 | |
29 | 29 | MMATH_LINK_FLAGS=--search-lib-files-dir '$(ROOT)/mmath' --init-file '$(ROOT)/mmath/mmath.init' |
@@ -37,6 +37,7 @@ SAFFRON_TGA_LINK_FLAGS=--search-lib-files-dir '$(ROOT)/util/tga' --init-file '$( | ||
37 | 37 | SAFFRON_GLOW_LINK_FLAGS=--search-lib-files-dir '$(ROOT)/util/glow' --init-file '$(ROOT)/util/glow/saffron_glow.init' $(GLOW_INCLUDE_FLAGS) |
38 | 38 | SAFFRON_WINDOW_LINK_FLAGS=--search-lib-files-dir '$(ROOT)/util/window' --init-file '$(ROOT)/util/window/saffron_window.init' |
39 | 39 | SAFFRON_DELEGATE_LINK_FLAGS=$(SAFFRON_WINDOW_LINK_FLAGS) --search-lib-files-dir '$(ROOT)/util/delegate' --init-file '$(ROOT)/util/delegate/saffron_delegate.init' |
40 | +SAFFRON_GLFW_LINK_FLAGS=$(SAFFRON_DELEGATE_LINK_FLAGS) --search-lib-files-dir '$(ROOT)/util/glfw' --init-file '$(ROOT)/util/glfw/saffron_glfw.init' | |
40 | 41 | |
41 | 42 | # Generated source. |
42 | 43 | saffron.gl.buffer.inc: saffron.gl_gen.awk saffron.gl.buffer.csv |
@@ -91,10 +92,17 @@ build_saffron_window: | ||
91 | 92 | util/window/libsaffron_delegate.$(SO): build_saffron_delegate |
92 | 93 | build_saffron_delegate: build_saffron_window |
93 | 94 | cd util/delegate && $(MMC) $(SAFFRON_MMC_OPTS) $(MMATH_LINK_FLAGS) $(SAFFRON_LINK_FLAGS) $(SAFFRON_WINDOW_LINK_FLAGS) --make libsaffron_delegate $(STATIC_MMC_FLAGS) |
94 | - cd util/delegate && $(MMC) $(SAFFRON_MMC_OPTS) $(MMATH_LINK_FLAGS) $(SAFFRON_LINK_FLAGS) $(SAFFRON_WINDOW_LINK_FLAGS) --make libsaffron_delegate $(SHARED_MMC_FLAGS) | |
95 | + cd util/delegate && $(MMC) $(SAFFRON_MMC_OPTS) $(MMATH_LINK_FLAGS) $(SAFFRON_LINK_FLAGS) $(SAFFRON_WINDOW_LINK_FLAGS) --make libsaffron_delegate $(SHARED_MMC_FLAGS) -L '$(ROOT)/util/window' -lsaffron_window | |
95 | 96 | install util/delegate/libsaffron_delegate.$(SO) demo/ |
96 | 97 | install util/delegate/libsaffron_delegate.$(SO) test/ |
97 | 98 | |
99 | +util/glow/libsaffron_glfw.$(SO): build_saffron_glfw | |
100 | +build_saffron_glfw: build_saffron_delegate build_saffron_window build_saffron | |
101 | + cd util/glfw && $(MMC) $(SAFFRON_MMC_OPTS) $(MMATH_LINK_FLAGS) $(SAFFRON_LINK_FLAGS) $(SAFFRON_DELEGATE_LINK_FLAGS) --make libsaffron_glfw $(STATIC_MMC_FLAGS) | |
102 | + cd util/glfw && $(MMC) $(SAFFRON_MMC_OPTS) $(MMATH_LINK_FLAGS) $(SAFFRON_LINK_FLAGS) $(SAFFRON_DELEGATE_LINK_FLAGS) --make libsaffron_glfw $(SHARED_MMC_FLAGS) -L '$(ROOT)/util/window' -lsaffron_window -L '$(ROOT)/util/delegate' -lsaffron_delegate -L '$(ROOT)' -lsaffron -lglfw -lGL | |
103 | + install util/glfw/libsaffron_glfw.$(SO) demo/ | |
104 | + install util/glfw/libsaffron_glfw.$(SO) test/ | |
105 | + | |
98 | 106 | build_saffron_tga: |
99 | 107 | cd util/tga && $(MMC) $(SAFFRON_MMC_OPTS) --make libsaffron_tga $(STATIC_MMC_FLAGS) |
100 | 108 |
@@ -118,6 +126,9 @@ build_saffron_cube_demo: build_demo_utils demo/res/crate.tga build_saffron build | ||
118 | 126 | build_saffron_shader_demo: build_demo_utils build_saffron build_saffron_glow |
119 | 127 | cd demo && $(MMC) $(SAFFRON_MMC_OPTS) -E --make saffron_shader_demo $(MMATH_LINK_FLAGS) $(SAFFRON_LINK_FLAGS) $(SAFFRON_GLOW_LINK_FLAGS) $(SAFFRON_WINDOW_LINK_FLAGS) -L '$(ROOT)/glow' -lglow -lX11 -lmmath -lsaffron -lsaffron_window -lsaffron_glow -lGL |
120 | 128 | |
129 | +build_saffron_glfw_demo: build_demo_utils demo/res/crate.tga build_saffron build_saffron_glfw build_saffron_tga | |
130 | + cd demo && $(MMC) $(SAFFRON_MMC_OPTS) -E --make saffron_glfw_demo $(MMATH_LINK_FLAGS) $(SAFFRON_LINK_FLAGS) $(SAFFRON_GLFW_LINK_FLAGS) $(SAFFRON_TGA_LINK_FLAGS) -lglfw -lmmath -lsaffron -lsaffron_window -lsaffron_delegate -lsaffron_glfw --link-object '$(ROOT)/util/tga/libsaffron_tga.a' -lGL | |
131 | + | |
121 | 132 | # Testing |
122 | 133 | build_transunit: |
123 | 134 | cd test/transunit && $(MMC) $(SAFFRON_MMC_OPTS) --make libtransunit $(STATIC_MMC_FLAGS) |
@@ -149,6 +160,9 @@ clean_saffron_window: | ||
149 | 160 | clean_saffron_delegate: |
150 | 161 | DIR=util/delegate ; TARGET=saffron_delegate ; $(CLEAN_MERCURY) |
151 | 162 | |
163 | +clean_saffron_glfw: | |
164 | + DIR=util/glfw ; TARGET=saffron_glfw ; $(CLEAN_MERCURY) | |
165 | + | |
152 | 166 | clean_test_saffron_tga: |
153 | 167 | DIR=test ; TARGET=test_saffron_tga ; $(CLEAN_MERCURY) |
154 | 168 |
@@ -158,7 +172,10 @@ clean_saffron_cube_demo: | ||
158 | 172 | clean_saffron_shader_demo: |
159 | 173 | DIR=demo ; TARGET=saffron_shader_demo ; $(CLEAN_MERCURY) |
160 | 174 | |
161 | -CLEAN=clean_mmath clean_saffron clean_glow clean_saffron_glow clean_saffron_window clean_saffron_tga clean_saffron_delegate clean_glow clean_test_saffron_tga clean_saffron_cube_demo clean_saffron_cube_demo | |
175 | +clean_saffron_glfw_demo: | |
176 | + DIR=demo ; TARGET=saffron_glfw_demo ; $(CLEAN_MERCURY) | |
177 | + | |
178 | +CLEAN=clean_mmath clean_saffron clean_glow clean_saffron_glow clean_saffron_window clean_saffron_delegate clean_saffron_glfw clean_saffron_tga clean_saffron_delegate clean_glow clean_test_saffron_tga clean_saffron_cube_demo clean_saffron_cube_demo clean_saffron_glfw_demo | |
162 | 179 | clean: $(CLEAN) |
163 | 180 | rm -f demo/res/crate.tga demo/res/ctc24.tga demo/lib*.$(SO) # Remove the demo libraries we copied and libtimer. |
164 | 181 |
@@ -0,0 +1,487 @@ | ||
1 | +% Copyright (C) 2023 AlaskanEmily, Transnat Games | |
2 | +% | |
3 | +% this software is provided 'as-is', without any express or implied | |
4 | +% warranty. in no event will the authors be held liable for any damages | |
5 | +% arising from the use of this software. | |
6 | +% | |
7 | +% permission is granted to anyone to use this software for any purpose, | |
8 | +% including commercial applications, and to alter it and redistribute it | |
9 | +% freely, subject to the following restrictions: | |
10 | +% | |
11 | +% 1. the origin of this software must not be misrepresented; you must not | |
12 | +% claim that you wrote the original software. if you use this software | |
13 | +% in a product, an acknowledgment in the product documentation would be | |
14 | +% appreciated but is not required. | |
15 | +% 2. altered source versions must be plainly marked as such, and must not be | |
16 | +% misrepresented as being the original software. | |
17 | +% 3. this notice may not be removed or altered from any source distribution. | |
18 | + | |
19 | +:- module saffron_glfw. | |
20 | +%=============================================================================% | |
21 | +% Implementation of saffron_delegate using GLFW3. | |
22 | +% | |
23 | +% This is currently only implemented for C, but in the future this should | |
24 | +% include using GLFW through lwjgl for Java. | |
25 | +% | |
26 | +% There isn't a real need to use this in the C backend, as the OS X | |
27 | +% implementation of saffron_delegate and the glow backend of saffron_window are | |
28 | +% generally better and require fewer dependencies. You could use it if you | |
29 | +% only want to use saffron_delegate instead of saffron_window on some | |
30 | +% platforms, but doing that will mean more dependencies and more code running | |
31 | +% on the most common platforms. | |
32 | +:- interface. | |
33 | +%=============================================================================% | |
34 | + | |
35 | +:- use_module io. | |
36 | + | |
37 | +:- use_module saffron. | |
38 | +:- use_module saffron.gl. | |
39 | +:- use_module saffron_delegate. | |
40 | + | |
41 | +%-----------------------------------------------------------------------------% | |
42 | + | |
43 | +:- inst io_res_uniq == unique(io.ok(unique) ; io.error(ground)). | |
44 | +:- mode io_res_uo == (free >> io_res_uniq). | |
45 | + | |
46 | +%-----------------------------------------------------------------------------% | |
47 | + | |
48 | +:- type window. | |
49 | + | |
50 | +%-----------------------------------------------------------------------------% | |
51 | + | |
52 | +:- instance saffron.context(window). | |
53 | + | |
54 | +%-----------------------------------------------------------------------------% | |
55 | + | |
56 | +:- instance saffron.gl.context(window). | |
57 | + | |
58 | +%-----------------------------------------------------------------------------% | |
59 | + | |
60 | +:- instance saffron_delegate.window(window). | |
61 | + | |
62 | +%-----------------------------------------------------------------------------% | |
63 | +% create_window(T, W, H, Title, GLMajor, GLMinor, Result, !IO). | |
64 | +:- pred create_window(T, int, int, string, int, int, io.res(window), io.io, io.io) | |
65 | + <= saffron_delegate.delegate(T). | |
66 | +:- mode create_window(in, in, in, in, in, in, io_res_uo, di, uo) is det. | |
67 | + | |
68 | +%=============================================================================% | |
69 | +:- implementation. | |
70 | +%=============================================================================% | |
71 | + | |
72 | +:- use_module maybe. | |
73 | +:- use_module bool. | |
74 | + | |
75 | +:- pragma foreign_import_module("C", saffron_window). | |
76 | +:- pragma foreign_import_module("C", saffron_delegate). | |
77 | + | |
78 | +%-----------------------------------------------------------------------------% | |
79 | + | |
80 | +:- pragma foreign_decl("C", | |
81 | + " | |
82 | +#include <GLFW/glfw3.h> | |
83 | +extern MR_Bool saffron_glfw_init_ok; | |
84 | + | |
85 | +void SaffronGLFW_KeyCallback( | |
86 | + GLFWwindow *win, | |
87 | + int key, | |
88 | + int scancode, | |
89 | + int action, | |
90 | + int mods); | |
91 | + | |
92 | +void SaffronGLFW_MouseButtonCallback( | |
93 | + GLFWwindow *win, | |
94 | + int button, | |
95 | + int action, | |
96 | + int mods); | |
97 | + | |
98 | +void SaffronGLFW_MouseMotionCallback( | |
99 | + GLFWwindow *win, | |
100 | + double x, | |
101 | + double y); | |
102 | + | |
103 | +#define SAFFRON_GLFW_GET_ERROR(OK, ERR) do{ \\ | |
104 | + const char *SAFFRON_GLFW_GET_ERROR_err; \\ | |
105 | + if(glfwGetError(&SAFFRON_GLFW_GET_ERROR_err) == 0){ \\ | |
106 | + (OK) = 1; \\ | |
107 | + (ERR) = (MR_String)""<NULL>""; \\ | |
108 | + } \\ | |
109 | + else{ \\ | |
110 | + (OK) = 0; \\ | |
111 | + const long SAFFRON_GLFW_GET_ERROR_len = \\ | |
112 | + strlen(SAFFRON_GLFW_GET_ERROR_err); \\ | |
113 | + MR_allocate_aligned_string_msg( \\ | |
114 | + ERR, \\ | |
115 | + SAFFRON_GLFW_GET_ERROR_len, \\ | |
116 | + MR_ALLOC_ID); \\ | |
117 | + memcpy((ERR), \\ | |
118 | + SAFFRON_GLFW_GET_ERROR_err, \\ | |
119 | + SAFFRON_GLFW_GET_ERROR_len + 1); \\ | |
120 | + } \\ | |
121 | +}while(0) | |
122 | + | |
123 | + "). | |
124 | + | |
125 | +%-----------------------------------------------------------------------------% | |
126 | + | |
127 | +:- pragma foreign_code("C", | |
128 | + " | |
129 | +MR_Bool saffron_glfw_init_ok = MR_NO; | |
130 | + | |
131 | +void SaffronGLFW_KeyCallback( | |
132 | + GLFWwindow *win, | |
133 | + int key, | |
134 | + int scancode, | |
135 | + int action, | |
136 | + int mods){ | |
137 | + | |
138 | + MR_Word delegate; | |
139 | + MR_Word mr_key; | |
140 | + delegate = (MR_Word)glfwGetWindowUserPointer(win); | |
141 | + | |
142 | + /* Create the key data. */ | |
143 | + if(key >= 32 && key <= 96){ | |
144 | + mr_key = Saffron_CreateKey(key); | |
145 | + } | |
146 | + else{ | |
147 | + switch(key){ | |
148 | + case GLFW_KEY_ESCAPE: | |
149 | + mr_key = SAFFRON_KEY_ESCAPE; | |
150 | + break; | |
151 | + case GLFW_KEY_ENTER: | |
152 | + mr_key = SAFFRON_KEY_ENTER; | |
153 | + break; | |
154 | + case GLFW_KEY_TAB: | |
155 | + mr_key = SAFFRON_KEY_TAB; | |
156 | + break; | |
157 | + case GLFW_KEY_BACKSPACE: | |
158 | + mr_key = SAFFRON_KEY_BACKSPACE; | |
159 | + break; | |
160 | + case GLFW_KEY_DELETE: | |
161 | + mr_key = SAFFRON_KEY_DELETE; | |
162 | + break; | |
163 | + case GLFW_KEY_RIGHT: | |
164 | + mr_key = SAFFRON_KEY_RIGHT_ARROW; | |
165 | + break; | |
166 | + case GLFW_KEY_LEFT: | |
167 | + mr_key = SAFFRON_KEY_LEFT_ARROW; | |
168 | + break; | |
169 | + case GLFW_KEY_DOWN: | |
170 | + mr_key = SAFFRON_KEY_DOWN_ARROW; | |
171 | + break; | |
172 | + case GLFW_KEY_UP: | |
173 | + mr_key = SAFFRON_KEY_UP_ARROW; | |
174 | + break; | |
175 | + case GLFW_KEY_LEFT_SHIFT: /* FALLTHROUGH */ | |
176 | + case GLFW_KEY_RIGHT_SHIFT: | |
177 | + mr_key = SAFFRON_KEY_SHIFT; | |
178 | + break; | |
179 | + case GLFW_KEY_LEFT_CONTROL: /* FALLTHROUGH */ | |
180 | + case GLFW_KEY_RIGHT_CONTROL: | |
181 | + mr_key = SAFFRON_KEY_CONTROL; | |
182 | + break; | |
183 | + default: | |
184 | + return; | |
185 | + } | |
186 | + Saffron_CreateSpecialKey(mr_key, &mr_key); | |
187 | + } | |
188 | + switch(action){ | |
189 | + case GLFW_PRESS: | |
190 | + Saffron_DelegateKeyPress(delegate, mr_key); | |
191 | + break; | |
192 | + case GLFW_RELEASE: | |
193 | + Saffron_DelegateKeyRelease(delegate, mr_key); | |
194 | + break; | |
195 | + } | |
196 | +} | |
197 | + | |
198 | +void SaffronGLFW_MouseButtonCallback( | |
199 | + GLFWwindow *win, | |
200 | + int button, | |
201 | + int action, | |
202 | + int mods){ | |
203 | + | |
204 | + MR_Word delegate; | |
205 | + MR_Word mr_button; | |
206 | + double x, y; | |
207 | + delegate = (MR_Word)glfwGetWindowUserPointer(win); | |
208 | + | |
209 | + switch(button){ | |
210 | + case GLFW_MOUSE_BUTTON_LEFT: | |
211 | + mr_button = SAFFRON_BUTTON_LEFT; | |
212 | + break; | |
213 | + case GLFW_MOUSE_BUTTON_RIGHT: | |
214 | + mr_button = SAFFRON_BUTTON_RIGHT; | |
215 | + break; | |
216 | + case GLFW_MOUSE_BUTTON_MIDDLE: | |
217 | + mr_button = SAFFRON_BUTTON_MIDDLE; | |
218 | + break; | |
219 | + default: | |
220 | + return; | |
221 | + } | |
222 | + glfwGetCursorPos(win, &x, &y); | |
223 | + switch(action){ | |
224 | + case GLFW_PRESS: | |
225 | + Saffron_DelegateButtonPress(delegate, mr_button, x, y); | |
226 | + break; | |
227 | + case GLFW_RELEASE: | |
228 | + Saffron_DelegateButtonRelease(delegate, mr_button, x, y); | |
229 | + break; | |
230 | + } | |
231 | +} | |
232 | + | |
233 | +void SaffronGLFW_MouseMotionCallback( | |
234 | + GLFWwindow *win, | |
235 | + double x, | |
236 | + double y){ | |
237 | + | |
238 | + MR_Word delegate; | |
239 | + delegate = (MR_Word)glfwGetWindowUserPointer(win); | |
240 | + | |
241 | + Saffron_DelegateMouseMotion(delegate, x, y); | |
242 | +} | |
243 | + | |
244 | + "). | |
245 | + | |
246 | +%-----------------------------------------------------------------------------% | |
247 | + | |
248 | +:- pragma foreign_type("C", window, "GLFWwindow*"). | |
249 | + | |
250 | +:- type delegate == saffron_delegate.delegate_private. | |
251 | + | |
252 | +%-----------------------------------------------------------------------------% | |
253 | + | |
254 | +:- impure pred glfw_initialize is det. | |
255 | +glfw_initialize. | |
256 | +:- pragma foreign_proc("C", | |
257 | + glfw_initialize, | |
258 | + [will_not_call_mercury, thread_safe, will_not_throw_exception], | |
259 | + "saffron_glfw_init_ok = glfwInit();"). | |
260 | + | |
261 | +:- initialize glfw_initialize/0. | |
262 | + | |
263 | +%-----------------------------------------------------------------------------% | |
264 | + | |
265 | +:- impure pred glfw_finalize is det. | |
266 | +glfw_finalize. | |
267 | +:- pragma foreign_proc("C", | |
268 | + glfw_finalize, | |
269 | + [will_not_call_mercury, thread_safe, will_not_throw_exception], | |
270 | + "glfwTerminate();"). | |
271 | + | |
272 | +:- finalize glfw_finalize/0. | |
273 | + | |
274 | +%-----------------------------------------------------------------------------% | |
275 | + | |
276 | +:- func create_window_ok(window::di) = (io.res(window)::uo) is det. | |
277 | +create_window_ok(Ctx) = io.ok(Ctx). | |
278 | +:- pragma foreign_export("C", | |
279 | + create_window_ok(di) = (uo), | |
280 | + "SaffronGLFW_CreateWindowOK"). | |
281 | + | |
282 | +%-----------------------------------------------------------------------------% | |
283 | + | |
284 | +:- func create_window_error(string::di) = (io.res(window)::io_res_uo) is det. | |
285 | +create_window_error(Str) = io.error(io.make_io_error(Str)). | |
286 | +:- pragma foreign_export("C", | |
287 | + create_window_error(di) = (io_res_uo), | |
288 | + "SaffronGLFW_CreateWindowError"). | |
289 | + | |
290 | +%-----------------------------------------------------------------------------% | |
291 | + | |
292 | +:- pred make_current(window::in, io.io::di, io.io::uo) is det. | |
293 | +:- pragma foreign_proc("C", | |
294 | + make_current(Win::in, IOi::di, IOo::uo), | |
295 | + [will_not_call_mercury, promise_pure, thread_safe, will_not_throw_exception, | |
296 | + does_not_affect_liveness, may_duplicate], | |
297 | + " | |
298 | + IOo = IOi; | |
299 | + glfwMakeContextCurrent(Win); | |
300 | + "). | |
301 | + | |
302 | +%-----------------------------------------------------------------------------% | |
303 | + | |
304 | +:- pred load_function(window, string, maybe.maybe_error(saffron.ctx_function), io.io, io.io). | |
305 | +:- mode load_function(in, in, out, di, uo) is det. | |
306 | +:- pragma foreign_proc("C", | |
307 | + load_function(Win::in, Name::in, MaybeFunc::out, IOi::di, IOo::uo), | |
308 | + [will_not_call_mercury, promise_pure, thread_safe, will_not_throw_exception, | |
309 | + does_not_affect_liveness, may_duplicate], | |
310 | + " | |
311 | + GLFWglproc proc; | |
312 | + MR_String err; | |
313 | + int ok; | |
314 | + IOo = IOi; | |
315 | + (void)Win; | |
316 | + proc = glfwGetProcAddress(Name); | |
317 | + SAFFRON_GLFW_GET_ERROR(ok, err); | |
318 | + MaybeFunc = ok ? | |
319 | + Saffron_CreateCtxFunctionOK((MR_Word)proc) : | |
320 | + Saffron_CreateCtxFunctionError(err); | |
321 | + "). | |
322 | + | |
323 | +%-----------------------------------------------------------------------------% | |
324 | + | |
325 | +:- instance saffron.context(window) where [ | |
326 | + pred(saffron.make_current/3) is saffron_glfw.make_current, | |
327 | + pred(saffron.load_function/5) is saffron_glfw.load_function | |
328 | +]. | |
329 | + | |
330 | +%-----------------------------------------------------------------------------% | |
331 | + | |
332 | +:- instance saffron.gl.context(window) where []. | |
333 | + | |
334 | +%-----------------------------------------------------------------------------% | |
335 | + | |
336 | +:- pred show_window(window::in, io.io::di, io.io::uo) is det. | |
337 | +:- pragma foreign_proc("C", | |
338 | + show_window(Win::in, IOi::di, IOo::uo), | |
339 | + [will_not_call_mercury, promise_pure, thread_safe, will_not_throw_exception, | |
340 | + does_not_affect_liveness, may_duplicate], | |
341 | + " | |
342 | + IOo = IOi; | |
343 | + glfwShowWindow(Win); | |
344 | + "). | |
345 | + | |
346 | +%-----------------------------------------------------------------------------% | |
347 | + | |
348 | +:- pred hide_window(window::in, io.io::di, io.io::uo) is det. | |
349 | +:- pragma foreign_proc("C", | |
350 | + hide_window(Win::in, IOi::di, IOo::uo), | |
351 | + [will_not_call_mercury, promise_pure, thread_safe, will_not_throw_exception, | |
352 | + does_not_affect_liveness, may_duplicate], | |
353 | + " | |
354 | + IOo = IOi; | |
355 | + glfwHideWindow(Win); | |
356 | + "). | |
357 | + | |
358 | +%-----------------------------------------------------------------------------% | |
359 | + | |
360 | +:- pred update(window::in, bool.bool::uo, io.io::di, io.io::uo) is det. | |
361 | +:- pragma foreign_proc("C", | |
362 | + update(Win::in, ShouldQuit::uo, IOi::di, IOo::uo), | |
363 | + [will_not_call_mercury, promise_pure, thread_safe, will_not_throw_exception, | |
364 | + does_not_affect_liveness, may_duplicate], | |
365 | + " | |
366 | + int w, h; | |
367 | + MR_Word delegate; | |
368 | + IOo = IOi; | |
369 | + ShouldQuit = glfwWindowShouldClose(Win); | |
370 | + if(!ShouldQuit){ | |
371 | + glfwMakeContextCurrent(Win); | |
372 | + glfwGetFramebufferSize(Win, &w, &h); | |
373 | + glViewport(0, 0, w, h); | |
374 | + delegate = (MR_Word)glfwGetWindowUserPointer(Win); | |
375 | + Saffron_DelegateRedraw(delegate); | |
376 | + glfwSwapBuffers(Win); | |
377 | + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); | |
378 | + glfwPollEvents(); | |
379 | + } | |
380 | + "). | |
381 | + | |
382 | +%-----------------------------------------------------------------------------% | |
383 | + | |
384 | +:- instance saffron_delegate.window(window) where [ | |
385 | + pred(saffron_delegate.show_window/3) is saffron_glfw.show_window, | |
386 | + pred(saffron_delegate.hide_window/3) is saffron_glfw.hide_window, | |
387 | + pred(saffron_delegate.update/4) is saffron_glfw.update | |
388 | +]. | |
389 | + | |
390 | +%-----------------------------------------------------------------------------% | |
391 | + | |
392 | +:- type hint ---> | |
393 | + context_major ; | |
394 | + context_minor ; | |
395 | + doublebuffer ; | |
396 | + red_bits ; | |
397 | + green_bits ; | |
398 | + blue_bits ; | |
399 | + alpha_bits ; | |
400 | + retina ; | |
401 | + no_error. | |
402 | + | |
403 | +:- pragma foreign_enum("C", hint/0, [ | |
404 | + context_major - "GLFW_CONTEXT_VERSION_MAJOR", | |
405 | + context_minor - "GLFW_CONTEXT_VERSION_MINOR", | |
406 | + doublebuffer - "GLFW_DOUBLEBUFFER", | |
407 | + red_bits - "GLFW_RED_BITS", | |
408 | + green_bits - "GLFW_GREEN_BITS", | |
409 | + blue_bits - "GLFW_BLUE_BITS", | |
410 | + alpha_bits - "GLFW_ALPHA_BITS", | |
411 | + retina - "GLFW_COCOA_RETINA_FRAMEBUFFER", | |
412 | + no_error - "GLFW_CONTEXT_NO_ERROR" | |
413 | +]). | |
414 | + | |
415 | +%-----------------------------------------------------------------------------% | |
416 | + | |
417 | +:- pred window_hint(hint::in, int::in, io.io::di, io.io::uo) is det. | |
418 | +:- pred reset_window_hints(io.io::di, io.io::uo) is det. | |
419 | + | |
420 | +:- pragma foreign_proc("C", | |
421 | + window_hint(Hint::in, I::in, IOi::di, IOo::uo), | |
422 | + [promise_pure, will_not_call_mercury, will_not_throw_exception, thread_safe, | |
423 | + may_duplicate, does_not_affect_liveness], | |
424 | + " | |
425 | + IOo = IOi; | |
426 | + glfwWindowHint(Hint, I); | |
427 | + "). | |
428 | + | |
429 | +:- pragma foreign_proc("C", | |
430 | + reset_window_hints(IOi::di, IOo::uo), | |
431 | + [promise_pure, will_not_call_mercury, will_not_throw_exception, thread_safe, | |
432 | + may_duplicate, does_not_affect_liveness], | |
433 | + " | |
434 | + IOo = IOi; | |
435 | + glfwDefaultWindowHints(); | |
436 | + "). | |
437 | + | |
438 | +%-----------------------------------------------------------------------------% | |
439 | + | |
440 | +:- pred create_window(delegate, int, int, string, io.res(window), io.io, io.io). | |
441 | +:- mode create_window(in, in, in, in, io_res_uo, di, uo) is det. | |
442 | + | |
443 | +:- pragma foreign_proc("C", | |
444 | + create_window(D::in, W::in, H::in, Title::in, Result::io_res_uo, IOi::di, IOo::uo), | |
445 | + [promise_pure, will_not_call_mercury, will_not_throw_exception, thread_safe, | |
446 | + may_duplicate, does_not_affect_liveness], | |
447 | + " | |
448 | + GLFWwindow *window; | |
449 | + MR_String err; | |
450 | + int ok; | |
451 | + | |
452 | + IOo = IOi; | |
453 | + if(!saffron_glfw_init_ok){ | |
454 | + ok = 0; | |
455 | + err = (MR_String)""GLFW did not initialize""; | |
456 | + } | |
457 | + else{ | |
458 | + // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); | |
459 | + window = glfwCreateWindow(W, H, Title, NULL, NULL); | |
460 | + SAFFRON_GLFW_GET_ERROR(ok, err); | |
461 | + } | |
462 | + if(ok && window != NULL){ | |
463 | + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); | |
464 | + glfwSetWindowUserPointer(window, (void*)D); | |
465 | + glfwSetKeyCallback(window, SaffronGLFW_KeyCallback); | |
466 | + glfwSetMouseButtonCallback(window, SaffronGLFW_MouseButtonCallback); | |
467 | + glfwSetCursorPosCallback(window, SaffronGLFW_MouseMotionCallback); | |
468 | + Result = SaffronGLFW_CreateWindowOK(window); | |
469 | + } | |
470 | + else{ | |
471 | + Result = SaffronGLFW_CreateWindowError(err); | |
472 | + } | |
473 | + "). | |
474 | + | |
475 | +%-----------------------------------------------------------------------------% | |
476 | + | |
477 | +% create_window(T, W, H, Title, GLMajor, GLMinor, Result, !IO). | |
478 | +create_window(D, W, H, Title, GLMajor, GLMinor, Result, !IO) :- | |
479 | + reset_window_hints(!IO), | |
480 | + window_hint(context_major, GLMajor, !IO), | |
481 | + window_hint(context_minor, GLMinor, !IO), | |
482 | + window_hint(retina, 1, !IO), | |
483 | + Delegate = saffron_delegate.create_delegate_private(D), | |
484 | + create_window(Delegate, W, H, Title, Result, !IO). | |
485 | + | |
486 | +%-----------------------------------------------------------------------------% | |
487 | + |