Skip to content

Commit 779457f

Browse files
committed
add opengl part 3
1 parent 05d9795 commit 779457f

File tree

6 files changed

+906
-272
lines changed

6 files changed

+906
-272
lines changed

output/index.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ <h1>andy@andyleclair.dev$><span class="blink">_</span></h1>
2121
<h2 class="text-xl">Blog!</h2>
2222
<ul>
2323
<li>
24-
2024-09-10 - <a href="posts/2024/09-10-2024-gltest.html">GLtest</a>
24+
2024-09-11 - <a href="posts/2024/09-11-opengl-part-3.html">OpenGL Part 3</a>
2525
</li><li>
2626
2024-09-10 - <a href="posts/2024/09-10-opengl-part-2.html">OpenGL Part 2</a>
27+
</li><li>
28+
2024-09-09 - <a href="posts/2024/09-09-gltest.html">GLtest</a>
2729
</li><li>
2830
2024-08-28 - <a href="posts/2024/08-28-readme.html">README</a>
2931
</li><li>

output/posts/2024/09-10-2024-gltest.html renamed to output/posts/2024/09-09-gltest.html

Lines changed: 175 additions & 175 deletions
Large diffs are not rendered by default.

output/posts/2024/09-10-opengl-part-2.html

Lines changed: 96 additions & 96 deletions
Large diffs are not rendered by default.

output/posts/2024/09-11-opengl-part-3.html

Lines changed: 327 additions & 0 deletions
Large diffs are not rendered by default.
File renamed without changes.

posts/2024/09-11-opengl-part-3.md

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
%{
2+
title: "OpenGL Part 3",
3+
description: "Layering shapes, displaying FPS",
4+
author: "Andy LeClair",
5+
tags: ["elixir", "opengl", "wxwidgets", "graphics"],
6+
related_listening: "https://www.youtube.com/watch?v=bwsjQbf0czc",
7+
}
8+
---
9+
10+
Continuing on, I wanted to try the exercises at the bottom of [this chapter](https://learnopengl.com/Getting-started/Hello-Triangle), and this is what I came up with.
11+
12+
```elixir
13+
defmodule GlTest.Window do
14+
import WxRecords
15+
16+
@behaviour :wx_object
17+
18+
def start_link(_) do
19+
:wx_object.start_link(__MODULE__, [], [])
20+
{:ok, self()}
21+
end
22+
23+
@impl :wx_object
24+
def init(_) do
25+
opts = [size: {800, 600}]
26+
wx = :wx.new()
27+
frame = :wxFrame.new(wx, :wx_const.wx_id_any(), ~c"Hello", opts)
28+
29+
:wxWindow.connect(frame, :close_window)
30+
:wxFrame.show(frame)
31+
32+
gl_attrib = [
33+
attribList: [
34+
:wx_const.wx_gl_core_profile(),
35+
:wx_const.wx_gl_major_version(),
36+
3,
37+
:wx_const.wx_gl_minor_version(),
38+
3,
39+
:wx_const.wx_gl_doublebuffer(),
40+
0
41+
]
42+
]
43+
44+
canvas = :wxGLCanvas.new(frame, opts ++ gl_attrib)
45+
ctx = :wxGLContext.new(canvas)
46+
:wxGLCanvas.setCurrent(canvas, ctx)
47+
48+
{shader_program, vao1, vao2, rect_vao} = init_opengl()
49+
frame_counter = :counters.new(1, [:atomics])
50+
51+
send(self(), :update)
52+
now = System.monotonic_time(:millisecond)
53+
54+
{frame,
55+
%{
56+
last_time: now,
57+
frame: frame,
58+
frame_counter: frame_counter,
59+
canvas: canvas,
60+
shader_program: shader_program,
61+
fps: 0,
62+
vao1: vao1,
63+
vao2: vao2,
64+
rect_vao: rect_vao
65+
}}
66+
end
67+
68+
@vertex_source """
69+
#version 330 core
70+
layout (location = 0) in vec3 aPos;
71+
void main() {
72+
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
73+
}\0
74+
"""
75+
|> String.to_charlist()
76+
77+
@fragment_source """
78+
#version 330 core
79+
out vec4 FragColor;
80+
void main() {
81+
FragColor = vec4(0.44f, 0.35f, 0.5f, 1.0f);
82+
}\0
83+
"""
84+
|> String.to_charlist()
85+
86+
def init_opengl() do
87+
vertex_shader = :gl.createShader(:gl_const.gl_vertex_shader())
88+
:gl.shaderSource(vertex_shader, [@vertex_source])
89+
:gl.compileShader(vertex_shader)
90+
91+
fragment_shader = :gl.createShader(:gl_const.gl_fragment_shader())
92+
:gl.shaderSource(fragment_shader, [@fragment_source])
93+
:gl.compileShader(fragment_shader)
94+
95+
shader_program = :gl.createProgram()
96+
:gl.attachShader(shader_program, vertex_shader)
97+
:gl.attachShader(shader_program, fragment_shader)
98+
:gl.linkProgram(shader_program)
99+
100+
:gl.deleteShader(vertex_shader)
101+
:gl.deleteShader(fragment_shader)
102+
103+
vertices = triangle_vertices()
104+
vertices_2 = triangle_vertices_2()
105+
106+
[vao1, vao2, rect_vao] = :gl.genVertexArrays(3)
107+
[vbo1, vbo2, rect_vbo, ebo] = :gl.genBuffers(4)
108+
109+
for {vertex_array, vertex_buffer, vertices} <- [
110+
{vao1, vbo1, vertices},
111+
{vao2, vbo2, vertices_2}
112+
] do
113+
:gl.bindVertexArray(vertex_array)
114+
115+
:gl.bindBuffer(:gl_const.gl_array_buffer(), vertex_buffer)
116+
117+
:gl.bufferData(
118+
:gl_const.gl_array_buffer(),
119+
byte_size(vertices),
120+
vertices,
121+
:gl_const.gl_static_draw()
122+
)
123+
124+
:gl.vertexAttribPointer(
125+
0,
126+
3,
127+
:gl_const.gl_float(),
128+
:gl_const.gl_false(),
129+
3 * byte_size(<<0.0::float-size(32)>>),
130+
0
131+
)
132+
133+
:gl.enableVertexAttribArray(0)
134+
135+
:gl.bindBuffer(:gl_const.gl_array_buffer(), 0)
136+
137+
:gl.bindVertexArray(0)
138+
end
139+
140+
rect_vertices = rectangle_vertices()
141+
rect_indices = rectangle_indices()
142+
143+
:gl.bindVertexArray(rect_vao)
144+
:gl.bindBuffer(:gl_const.gl_array_buffer(), rect_vbo)
145+
146+
:gl.bufferData(
147+
:gl_const.gl_array_buffer(),
148+
byte_size(rect_vertices),
149+
rect_vertices,
150+
:gl_const.gl_static_draw()
151+
)
152+
153+
:gl.bindBuffer(:gl_const.gl_element_array_buffer(), ebo)
154+
155+
:gl.bufferData(
156+
:gl_const.gl_element_array_buffer(),
157+
byte_size(rect_indices),
158+
rect_indices,
159+
:gl_const.gl_static_draw()
160+
)
161+
162+
:gl.vertexAttribPointer(
163+
0,
164+
3,
165+
:gl_const.gl_float(),
166+
:gl_const.gl_false(),
167+
3 * byte_size(<<0.0::float-size(32)>>),
168+
0
169+
)
170+
171+
:gl.enableVertexAttribArray(0)
172+
{shader_program, vao1, vao2, rect_vao}
173+
end
174+
175+
@triangle_vertices [
176+
[0.0, 1.0, 0.0],
177+
[1.0, 0.0, 0.0],
178+
[1.0, 1.0, 0.0]
179+
]
180+
|> List.flatten()
181+
|> Enum.reduce(<<>>, fn el, acc -> acc <> <<el::float-native-size(32)>> end)
182+
def triangle_vertices do
183+
@triangle_vertices
184+
end
185+
186+
@triangle_vertices_2 [
187+
[-0.5, -0.5, 0.0],
188+
[0.5, -0.5, 0.0],
189+
[0.0, 0.5, 0.0]
190+
]
191+
|> List.flatten()
192+
|> Enum.reduce(<<>>, fn el, acc -> acc <> <<el::float-native-size(32)>> end)
193+
def triangle_vertices_2 do
194+
@triangle_vertices_2
195+
end
196+
197+
@rectangle_vertices [
198+
[0.5, 0.5, 0.0],
199+
[0.5, -0.5, 0.0],
200+
[-0.5, -0.5, 0.0],
201+
[-0.5, 0.5, 0.0]
202+
]
203+
|> List.flatten()
204+
|> Enum.reduce(<<>>, fn el, acc -> acc <> <<el::float-native-size(32)>> end)
205+
206+
def rectangle_vertices do
207+
@rectangle_vertices
208+
end
209+
210+
@rectangle_indices [[0, 1, 3], [1, 2, 3]]
211+
|> List.flatten()
212+
|> Enum.reduce(<<>>, fn el, acc -> acc <> <<el::native-size(32)>> end)
213+
def rectangle_indices do
214+
@rectangle_indices
215+
end
216+
217+
@impl :wx_object
218+
def handle_event(wx(event: wxClose()), state) do
219+
{:stop, :normal, state}
220+
end
221+
222+
@impl :wx_object
223+
def handle_info(:stop, %{canvas: canvas, fps_counter_label: fps_counter_label} = state) do
224+
:wxGLCanvas.destroy(canvas)
225+
:wxStaticText.destroy(fps_counter_label)
226+
227+
{:stop, :normal, state}
228+
end
229+
230+
@impl :wx_object
231+
def handle_info(:update, state) do
232+
state = render(state)
233+
234+
{:noreply, state}
235+
end
236+
237+
defp render(%{canvas: canvas} = state) do
238+
state =
239+
state
240+
|> update_frame_counter()
241+
|> draw()
242+
243+
:wxGLCanvas.swapBuffers(canvas)
244+
send(self(), :update)
245+
246+
state
247+
end
248+
249+
defp draw(%{frame: frame} = state) do
250+
:gl.clearColor(0.2, 0.1, 0.3, 1.0)
251+
:gl.clear(:gl_const.gl_color_buffer_bit())
252+
253+
:gl.useProgram(state.shader_program)
254+
255+
:gl.bindVertexArray(state.vao1)
256+
:gl.drawArrays(:gl_const.gl_triangles(), 0, 3)
257+
258+
:gl.bindVertexArray(state.vao2)
259+
:gl.drawArrays(:gl_const.gl_triangles(), 0, 3)
260+
261+
:gl.polygonMode(:gl_const.gl_front_and_back(), :gl_const.gl_line())
262+
:gl.bindVertexArray(state.rect_vao)
263+
:gl.drawElements(:gl_const.gl_triangles(), 6, :gl_const.gl_unsigned_int(), 0)
264+
:gl.polygonMode(:gl_const.gl_front_and_back(), :gl_const.gl_fill())
265+
266+
:wxWindow.setLabel(frame, ~c"FPS: #{state.fps}")
267+
268+
state
269+
end
270+
271+
def update_frame_counter(%{last_time: last_time, frame_counter: frame_counter} = state) do
272+
now = System.monotonic_time(:millisecond)
273+
elapsed = now - last_time
274+
275+
if elapsed > 100 do
276+
frames = :counters.get(frame_counter, 1)
277+
fps = (frames / elapsed * 1000) |> round()
278+
:counters.put(frame_counter, 1, 0)
279+
Map.merge(state, %{fps: fps, last_time: now})
280+
else
281+
:counters.add(frame_counter, 1, 1)
282+
state
283+
end
284+
end
285+
286+
def child_spec(opts) do
287+
%{
288+
id: __MODULE__,
289+
start: {__MODULE__, :start_link, [opts]},
290+
restart: :permanent
291+
}
292+
end
293+
end
294+
```
295+
296+
I was reading some of the related links and I saw this article with a bit about adding a FPS counter, and I thought that would be a fun exercise to try.
297+
298+
I'm using Erlang's `:counters` module to keep track of the number of frames rendered, and then every 100-ish ms I calculate the FPS and update the window title with the FPS.
299+
Then I zero out the frame counter.
300+
301+
I tried to layer a WxWidgets `StaticText` on top of the `GLCanvas` to display the FPS, but I couldn't get it to show up. That took
302+
me to [this](https://forums.wxwidgets.org/viewtopic.php?t=42109) thread, which says that if you're using OpenGL in Wx, it's _special_and that I should just avoid trying to mix the two.
303+
Eventually I will learn how to do it with OpenGL, which I think involves rendering the text as a bitmap?
304+
305+
[Last post](/2024/09-10-opengl-part-2), I mentioned that I didn't like how the formatter handled the vertexes, and I think I found a better way to do it this time. I'm using a list of lists of floats, flattening it, and then reducing it into a binary. It's a bit more verbose, but I think it's easier to read and understand, and since I've pushed it into a module attribute, it only gets evaluated at compile time.

0 commit comments

Comments
 (0)