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