/src/serenity/Userland/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022, Luke Wilde <lukew@serenityos.org> |
3 | | * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/Debug.h> |
9 | | #include <LibWeb/HTML/HTMLCanvasElement.h> |
10 | | #include <LibWeb/Layout/Node.h> |
11 | | #include <LibWeb/Painting/Paintable.h> |
12 | | #include <LibWeb/WebGL/WebGLRenderingContextBase.h> |
13 | | |
14 | | namespace Web::WebGL { |
15 | | |
16 | | // FIXME: Replace with constants defined in WebGL spec. |
17 | | #define GL_INVALID_OPERATION 0x0502 |
18 | | #define GL_INVALID_VALUE 0x0501 |
19 | 0 | #define GL_FRONT_AND_BACK 0x0408 |
20 | | |
21 | | WebGLRenderingContextBase::WebGLRenderingContextBase(JS::Realm& realm, HTML::HTMLCanvasElement& canvas_element, NonnullOwnPtr<OpenGLContext> context, WebGLContextAttributes context_creation_parameters, WebGLContextAttributes actual_context_parameters) |
22 | 0 | : PlatformObject(realm) |
23 | 0 | , m_canvas_element(canvas_element) |
24 | 0 | , m_context(move(context)) |
25 | 0 | , m_context_creation_parameters(move(context_creation_parameters)) |
26 | 0 | , m_actual_context_parameters(move(actual_context_parameters)) |
27 | 0 | { |
28 | 0 | } |
29 | | |
30 | 0 | WebGLRenderingContextBase::~WebGLRenderingContextBase() = default; |
31 | | |
32 | | void WebGLRenderingContextBase::visit_edges(Cell::Visitor& visitor) |
33 | 0 | { |
34 | 0 | Base::visit_edges(visitor); |
35 | 0 | visitor.visit(m_canvas_element); |
36 | 0 | } |
37 | | |
38 | | #define RETURN_WITH_WEBGL_ERROR_IF(condition, error) \ |
39 | 0 | if (condition) { \ |
40 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "{}(): error {:#x}", __func__, error); \ |
41 | 0 | set_error(error); \ |
42 | 0 | return; \ |
43 | 0 | } |
44 | | |
45 | | void WebGLRenderingContextBase::present() |
46 | 0 | { |
47 | 0 | if (!m_should_present) |
48 | 0 | return; |
49 | | |
50 | 0 | m_should_present = false; |
51 | | |
52 | | // "Before the drawing buffer is presented for compositing the implementation shall ensure that all rendering operations have been flushed to the drawing buffer." |
53 | | // FIXME: Is this the operation it means? |
54 | 0 | m_context->gl_flush(); |
55 | |
|
56 | 0 | m_context->present(*canvas_element().bitmap()); |
57 | | |
58 | | // "By default, after compositing the contents of the drawing buffer shall be cleared to their default values, as shown in the table above. |
59 | | // This default behavior can be changed by setting the preserveDrawingBuffer attribute of the WebGLContextAttributes object. |
60 | | // If this flag is true, the contents of the drawing buffer shall be preserved until the author either clears or overwrites them." |
61 | 0 | if (!m_context_creation_parameters.preserve_drawing_buffer) { |
62 | 0 | m_context->clear_buffer_to_default_values(); |
63 | 0 | } |
64 | 0 | } |
65 | | |
66 | | HTML::HTMLCanvasElement& WebGLRenderingContextBase::canvas_element() |
67 | 0 | { |
68 | 0 | return *m_canvas_element; |
69 | 0 | } |
70 | | |
71 | | HTML::HTMLCanvasElement const& WebGLRenderingContextBase::canvas_element() const |
72 | 0 | { |
73 | 0 | return *m_canvas_element; |
74 | 0 | } |
75 | | |
76 | | JS::NonnullGCPtr<HTML::HTMLCanvasElement> WebGLRenderingContextBase::canvas_for_binding() const |
77 | 0 | { |
78 | 0 | return *m_canvas_element; |
79 | 0 | } |
80 | | |
81 | | void WebGLRenderingContextBase::needs_to_present() |
82 | 0 | { |
83 | 0 | m_should_present = true; |
84 | |
|
85 | 0 | if (!canvas_element().paintable()) |
86 | 0 | return; |
87 | 0 | canvas_element().paintable()->set_needs_display(); |
88 | 0 | } |
89 | | |
90 | | void WebGLRenderingContextBase::set_error(GLenum error) |
91 | 0 | { |
92 | 0 | auto context_error = m_context->gl_get_error(); |
93 | 0 | if (context_error != GL_NO_ERROR) |
94 | 0 | m_error = context_error; |
95 | 0 | else |
96 | 0 | m_error = error; |
97 | 0 | } |
98 | | |
99 | | bool WebGLRenderingContextBase::is_context_lost() const |
100 | 0 | { |
101 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::is_context_lost()"); |
102 | 0 | return m_context_lost; |
103 | 0 | } |
104 | | |
105 | | Optional<Vector<String>> WebGLRenderingContextBase::get_supported_extensions() const |
106 | 0 | { |
107 | 0 | if (m_context_lost) |
108 | 0 | return Optional<Vector<String>> {}; |
109 | | |
110 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::get_supported_extensions()"); |
111 | | |
112 | | // FIXME: We don't currently support any extensions. |
113 | 0 | return Vector<String> {}; |
114 | 0 | } |
115 | | |
116 | | JS::Object* WebGLRenderingContextBase::get_extension(String const& name) const |
117 | 0 | { |
118 | 0 | if (m_context_lost) |
119 | 0 | return nullptr; |
120 | | |
121 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::get_extension(name='{}')", name); |
122 | | |
123 | | // FIXME: We don't currently support any extensions. |
124 | 0 | return nullptr; |
125 | 0 | } |
126 | | |
127 | | void WebGLRenderingContextBase::active_texture(GLenum texture) |
128 | 0 | { |
129 | 0 | if (m_context_lost) |
130 | 0 | return; |
131 | | |
132 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::active_texture(texture={:#08x})", texture); |
133 | 0 | m_context->gl_active_texture(texture); |
134 | 0 | } |
135 | | |
136 | | void WebGLRenderingContextBase::clear(GLbitfield mask) |
137 | 0 | { |
138 | 0 | if (m_context_lost) |
139 | 0 | return; |
140 | | |
141 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::clear(mask={:#08x})", mask); |
142 | 0 | m_context->gl_clear(mask); |
143 | | |
144 | | // FIXME: This should only be done if this is targeting the front buffer. |
145 | 0 | needs_to_present(); |
146 | 0 | } |
147 | | |
148 | | void WebGLRenderingContextBase::clear_color(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) |
149 | 0 | { |
150 | 0 | if (m_context_lost) |
151 | 0 | return; |
152 | | |
153 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::clear_color(red={}, green={}, blue={}, alpha={})", red, green, blue, alpha); |
154 | 0 | m_context->gl_clear_color(red, green, blue, alpha); |
155 | 0 | } |
156 | | |
157 | | void WebGLRenderingContextBase::clear_depth(GLclampf depth) |
158 | 0 | { |
159 | 0 | if (m_context_lost) |
160 | 0 | return; |
161 | | |
162 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::clear_depth(depth={})", depth); |
163 | 0 | m_context->gl_clear_depth(depth); |
164 | 0 | } |
165 | | |
166 | | void WebGLRenderingContextBase::clear_stencil(GLint s) |
167 | 0 | { |
168 | 0 | if (m_context_lost) |
169 | 0 | return; |
170 | | |
171 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::clear_stencil(s={:#08x})", s); |
172 | 0 | m_context->gl_clear_stencil(s); |
173 | 0 | } |
174 | | |
175 | | void WebGLRenderingContextBase::color_mask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) |
176 | 0 | { |
177 | 0 | if (m_context_lost) |
178 | 0 | return; |
179 | | |
180 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::color_mask(red={}, green={}, blue={}, alpha={})", red, green, blue, alpha); |
181 | 0 | m_context->gl_color_mask(red, green, blue, alpha); |
182 | 0 | } |
183 | | |
184 | | void WebGLRenderingContextBase::cull_face(GLenum mode) |
185 | 0 | { |
186 | 0 | if (m_context_lost) |
187 | 0 | return; |
188 | | |
189 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::cull_face(mode={:#08x})", mode); |
190 | 0 | m_context->gl_cull_face(mode); |
191 | 0 | } |
192 | | |
193 | | void WebGLRenderingContextBase::depth_func(GLenum func) |
194 | 0 | { |
195 | 0 | if (m_context_lost) |
196 | 0 | return; |
197 | | |
198 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::depth_func(func={:#08x})", func); |
199 | 0 | m_context->gl_depth_func(func); |
200 | 0 | } |
201 | | |
202 | | void WebGLRenderingContextBase::depth_mask(GLboolean mask) |
203 | 0 | { |
204 | 0 | if (m_context_lost) |
205 | 0 | return; |
206 | | |
207 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::depth_mask(mask={})", mask); |
208 | 0 | m_context->gl_depth_mask(mask); |
209 | 0 | } |
210 | | |
211 | | void WebGLRenderingContextBase::depth_range(GLclampf z_near, GLclampf z_far) |
212 | 0 | { |
213 | 0 | if (m_context_lost) |
214 | 0 | return; |
215 | | |
216 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::depth_range(z_near={}, z_far={})", z_near, z_far); |
217 | | |
218 | | // https://www.khronos.org/registry/webgl/specs/latest/1.0/#VIEWPORT_DEPTH_RANGE |
219 | | // "The WebGL API does not support depth ranges with where the near plane is mapped to a value greater than that of the far plane. A call to depthRange will generate an INVALID_OPERATION error if zNear is greater than zFar." |
220 | 0 | RETURN_WITH_WEBGL_ERROR_IF(z_near > z_far, GL_INVALID_OPERATION); |
221 | 0 | m_context->gl_depth_range(z_near, z_far); |
222 | 0 | } |
223 | | |
224 | | void WebGLRenderingContextBase::finish() |
225 | 0 | { |
226 | 0 | if (m_context_lost) |
227 | 0 | return; |
228 | | |
229 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::finish()"); |
230 | 0 | m_context->gl_finish(); |
231 | 0 | } |
232 | | |
233 | | void WebGLRenderingContextBase::flush() |
234 | 0 | { |
235 | 0 | if (m_context_lost) |
236 | 0 | return; |
237 | | |
238 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::flush()"); |
239 | 0 | m_context->gl_flush(); |
240 | 0 | } |
241 | | |
242 | | void WebGLRenderingContextBase::front_face(GLenum mode) |
243 | 0 | { |
244 | 0 | if (m_context_lost) |
245 | 0 | return; |
246 | | |
247 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::front_face(mode={:#08x})", mode); |
248 | 0 | m_context->gl_front_face(mode); |
249 | 0 | } |
250 | | |
251 | | GLenum WebGLRenderingContextBase::get_error() |
252 | 0 | { |
253 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::get_error()"); |
254 | | |
255 | | // "If the context's webgl context lost flag is set, returns CONTEXT_LOST_WEBGL the first time this method is called. Afterward, returns NO_ERROR until the context has been restored." |
256 | | // FIXME: The plan here is to make the context lost handler unconditionally set m_error to CONTEXT_LOST_WEBGL, which we currently do not have. |
257 | | // The idea for the unconditional set is that any potentially error generating functions will not execute when the context is lost. |
258 | 0 | if (m_error != GL_NO_ERROR || m_context_lost) { |
259 | 0 | auto last_error = m_error; |
260 | 0 | m_error = GL_NO_ERROR; |
261 | 0 | return last_error; |
262 | 0 | } |
263 | | |
264 | 0 | return m_context->gl_get_error(); |
265 | 0 | } |
266 | | |
267 | | void WebGLRenderingContextBase::line_width(GLfloat width) |
268 | 0 | { |
269 | 0 | if (m_context_lost) |
270 | 0 | return; |
271 | | |
272 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::line_width(width={})", width); |
273 | | |
274 | | // https://www.khronos.org/registry/webgl/specs/latest/1.0/#NAN_LINE_WIDTH |
275 | | // "In the WebGL API, if the width parameter passed to lineWidth is set to NaN, an INVALID_VALUE error is generated and the line width is not changed." |
276 | 0 | RETURN_WITH_WEBGL_ERROR_IF(isnan(width), GL_INVALID_VALUE); |
277 | 0 | m_context->gl_line_width(width); |
278 | 0 | } |
279 | | |
280 | | void WebGLRenderingContextBase::polygon_offset(GLfloat factor, GLfloat units) |
281 | 0 | { |
282 | 0 | if (m_context_lost) |
283 | 0 | return; |
284 | | |
285 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::polygon_offset(factor={}, units={})", factor, units); |
286 | 0 | m_context->gl_polygon_offset(factor, units); |
287 | 0 | } |
288 | | |
289 | | void WebGLRenderingContextBase::scissor(GLint x, GLint y, GLsizei width, GLsizei height) |
290 | 0 | { |
291 | 0 | if (m_context_lost) |
292 | 0 | return; |
293 | | |
294 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::scissor(x={}, y={}, width={}, height={})", x, y, width, height); |
295 | 0 | m_context->gl_scissor(x, y, width, height); |
296 | 0 | } |
297 | | |
298 | | void WebGLRenderingContextBase::stencil_op(GLenum fail, GLenum zfail, GLenum zpass) |
299 | 0 | { |
300 | 0 | if (m_context_lost) |
301 | 0 | return; |
302 | | |
303 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::stencil_op(fail={:#08x}, zfail={:#08x}, zpass={:#08x})", fail, zfail, zpass); |
304 | 0 | m_context->gl_stencil_op_separate(GL_FRONT_AND_BACK, fail, zfail, zpass); |
305 | 0 | } |
306 | | |
307 | | void WebGLRenderingContextBase::stencil_op_separate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass) |
308 | 0 | { |
309 | 0 | if (m_context_lost) |
310 | 0 | return; |
311 | | |
312 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::stencil_op_separate(face={:#08x}, fail={:#08x}, zfail={:#08x}, zpass={:#08x})", face, fail, zfail, zpass); |
313 | 0 | m_context->gl_stencil_op_separate(face, fail, zfail, zpass); |
314 | 0 | } |
315 | | |
316 | | void WebGLRenderingContextBase::viewport(GLint x, GLint y, GLsizei width, GLsizei height) |
317 | 0 | { |
318 | 0 | if (m_context_lost) |
319 | 0 | return; |
320 | | |
321 | 0 | dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContextBase::viewport(x={}, y={}, width={}, height={})", x, y, width, height); |
322 | 0 | m_context->gl_viewport(x, y, width, height); |
323 | 0 | } |
324 | | |
325 | | } |