2022-06-04 04:22:42 +01:00
/*
* Copyright ( c ) 2022 , Luke Wilde < lukew @ serenityos . org >
2024-11-29 20:49:59 +01:00
* Copyright ( c ) 2023 , Aliaksandr Kalenik < kalenik . aliaksandr @ gmail . com >
2022-06-04 04:22:42 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2024-11-29 20:49:59 +01:00
# include <LibJS/Runtime/ArrayBuffer.h>
# include <LibJS/Runtime/TypedArray.h>
2022-09-25 18:12:50 -06:00
# include <LibWeb/Bindings/Intrinsics.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/WebGLRenderingContextPrototype.h>
2022-06-04 04:22:42 +01:00
# include <LibWeb/HTML/HTMLCanvasElement.h>
2024-11-29 20:49:59 +01:00
# include <LibWeb/HTML/TraversableNavigable.h>
# include <LibWeb/Painting/Paintable.h>
2023-04-09 12:57:23 +02:00
# include <LibWeb/WebGL/EventNames.h>
2024-11-29 20:49:59 +01:00
# include <LibWeb/WebGL/WebGLBuffer.h>
2022-06-04 04:22:42 +01:00
# include <LibWeb/WebGL/WebGLContextEvent.h>
2024-11-29 20:49:59 +01:00
# include <LibWeb/WebGL/WebGLProgram.h>
2022-06-04 04:22:42 +01:00
# include <LibWeb/WebGL/WebGLRenderingContext.h>
2024-11-29 20:49:59 +01:00
# include <LibWeb/WebGL/WebGLShader.h>
# include <LibWeb/WebIDL/Buffers.h>
# include <GLES2/gl2.h>
# include <GLES2/gl2ext.h>
2022-06-04 04:22:42 +01:00
namespace Web : : WebGL {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( WebGLRenderingContext ) ;
2023-11-19 19:47:52 +01:00
2024-11-29 20:49:59 +01:00
# define RETURN_WITH_WEBGL_ERROR_IF(condition, error) \
if ( condition ) { \
dbgln_if ( WEBGL_CONTEXT_DEBUG , " {}(): error {:#x} " , __func__ , error ) ; \
set_error ( error ) ; \
return ; \
}
2022-06-04 04:22:42 +01:00
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#fire-a-webgl-context-event
2023-04-09 12:57:23 +02:00
static void fire_webgl_context_event ( HTML : : HTMLCanvasElement & canvas_element , FlyString const & type )
2022-06-04 04:22:42 +01:00
{
// To fire a WebGL context event named e means that an event using the WebGLContextEvent interface, with its type attribute [DOM4] initialized to e, its cancelable attribute initialized to true, and its isTrusted attribute [DOM4] initialized to true, is to be dispatched at the given object.
// FIXME: Consider setting a status message.
2023-08-13 13:05:26 +02:00
auto event = WebGLContextEvent : : create ( canvas_element . realm ( ) , type , WebGLContextEventInit { } ) ;
2022-06-04 04:22:42 +01:00
event - > set_is_trusted ( true ) ;
event - > set_cancelable ( true ) ;
2022-08-08 22:29:40 +02:00
canvas_element . dispatch_event ( * event ) ;
2022-06-04 04:22:42 +01:00
}
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#fire-a-webgl-context-creation-error
static void fire_webgl_context_creation_error ( HTML : : HTMLCanvasElement & canvas_element )
{
// 1. Fire a WebGL context event named "webglcontextcreationerror" at canvas, optionally with its statusMessage attribute set to a platform dependent string about the nature of the failure.
2023-04-09 12:57:23 +02:00
fire_webgl_context_event ( canvas_element , EventNames : : webglcontextcreationerror ) ;
2022-06-04 04:22:42 +01:00
}
2024-11-15 04:01:23 +13:00
JS : : ThrowCompletionOr < GC : : Ptr < WebGLRenderingContext > > WebGLRenderingContext : : create ( JS : : Realm & realm , HTML : : HTMLCanvasElement & canvas_element , JS : : Value options )
2022-06-04 04:22:42 +01:00
{
// We should be coming here from getContext being called on a wrapped <canvas> element.
2022-08-28 13:42:07 +02:00
auto context_attributes = TRY ( convert_value_to_context_attributes_dictionary ( canvas_element . vm ( ) , options ) ) ;
2022-06-04 04:22:42 +01:00
2024-11-29 20:17:25 +01:00
auto context = OpenGLContext : : create ( ) ;
2024-01-18 20:29:09 +01:00
if ( ! context ) {
2022-09-16 05:58:30 -06:00
fire_webgl_context_creation_error ( canvas_element ) ;
2024-11-15 04:01:23 +13:00
return GC : : Ptr < WebGLRenderingContext > { nullptr } ;
2022-09-16 05:58:30 -06:00
}
2024-01-18 20:29:09 +01:00
2024-11-14 05:50:17 +13:00
return realm . create < WebGLRenderingContext > ( realm , canvas_element , context . release_nonnull ( ) , context_attributes , context_attributes ) ;
2022-06-04 04:22:42 +01:00
}
2024-01-18 20:29:09 +01:00
WebGLRenderingContext : : WebGLRenderingContext ( JS : : Realm & realm , HTML : : HTMLCanvasElement & canvas_element , NonnullOwnPtr < OpenGLContext > context , WebGLContextAttributes context_creation_parameters , WebGLContextAttributes actual_context_parameters )
2024-11-29 20:49:59 +01:00
: PlatformObject ( realm )
, m_context ( move ( context ) )
, m_canvas_element ( canvas_element )
, m_context_creation_parameters ( context_creation_parameters )
, m_actual_context_parameters ( actual_context_parameters )
2022-09-02 15:53:02 +02:00
{
}
WebGLRenderingContext : : ~ WebGLRenderingContext ( ) = default ;
2023-08-07 08:41:28 +02:00
void WebGLRenderingContext : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2023-08-07 08:41:28 +02:00
Base : : initialize ( realm ) ;
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( WebGLRenderingContext ) ;
2023-01-10 06:28:20 -05:00
}
2024-11-29 20:49:59 +01:00
void WebGLRenderingContext : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_canvas_element ) ;
}
void WebGLRenderingContext : : present ( )
{
if ( ! m_should_present )
return ;
m_should_present = false ;
// "Before the drawing buffer is presented for compositing the implementation shall ensure that all rendering operations have been flushed to the drawing buffer."
glFlush ( ) ;
// "By default, after compositing the contents of the drawing buffer shall be cleared to their default values, as shown in the table above.
// This default behavior can be changed by setting the preserveDrawingBuffer attribute of the WebGLContextAttributes object.
// If this flag is true, the contents of the drawing buffer shall be preserved until the author either clears or overwrites them."
if ( ! m_context_creation_parameters . preserve_drawing_buffer ) {
m_context - > clear_buffer_to_default_values ( ) ;
}
}
GC : : Ref < HTML : : HTMLCanvasElement > WebGLRenderingContext : : canvas_for_binding ( ) const
{
return * m_canvas_element ;
}
void WebGLRenderingContext : : needs_to_present ( )
{
m_should_present = true ;
if ( ! m_canvas_element - > paintable ( ) )
return ;
m_canvas_element - > paintable ( ) - > set_needs_display ( ) ;
}
void WebGLRenderingContext : : set_error ( GLenum error )
{
auto context_error = glGetError ( ) ;
if ( context_error ! = GL_NO_ERROR )
m_error = context_error ;
else
m_error = error ;
}
bool WebGLRenderingContext : : is_context_lost ( ) const
{
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::is_context_lost() " ) ;
return m_context_lost ;
}
void WebGLRenderingContext : : set_size ( Gfx : : IntSize const & )
{
TODO ( ) ;
}
void WebGLRenderingContext : : reset_to_default_state ( )
{
}
RefPtr < Gfx : : PaintingSurface > WebGLRenderingContext : : surface ( )
{
TODO ( ) ;
}
Optional < Vector < String > > WebGLRenderingContext : : get_supported_extensions ( ) const
{
if ( m_context_lost )
return Optional < Vector < String > > { } ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::get_supported_extensions() " ) ;
// FIXME: We don't currently support any extensions.
return Vector < String > { } ;
}
JS : : Object * WebGLRenderingContext : : get_extension ( String const & name ) const
{
if ( m_context_lost )
return nullptr ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::get_extension(name='{}') " , name ) ;
// FIXME: We don't currently support any extensions.
return nullptr ;
}
void WebGLRenderingContext : : active_texture ( GLenum texture )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::active_texture(texture={:#08x}) " , texture ) ;
}
void WebGLRenderingContext : : clear ( GLbitfield mask )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::clear(mask={:#08x}) " , mask ) ;
// FIXME: This should only be done if this is targeting the front buffer.
needs_to_present ( ) ;
}
void WebGLRenderingContext : : clear_color ( GLclampf red , GLclampf green , GLclampf blue , GLclampf alpha )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::clear_color(red={}, green={}, blue={}, alpha={}) " , red , green , blue , alpha ) ;
}
void WebGLRenderingContext : : clear_depth ( GLclampf depth )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::clear_depth(depth={}) " , depth ) ;
}
void WebGLRenderingContext : : clear_stencil ( GLint s )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::clear_stencil(s={:#08x}) " , s ) ;
}
void WebGLRenderingContext : : color_mask ( GLboolean red , GLboolean green , GLboolean blue , GLboolean alpha )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::color_mask(red={}, green={}, blue={}, alpha={}) " , red , green , blue , alpha ) ;
}
void WebGLRenderingContext : : cull_face ( GLenum mode )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::cull_face(mode={:#08x}) " , mode ) ;
}
void WebGLRenderingContext : : depth_func ( GLenum func )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::depth_func(func={:#08x}) " , func ) ;
}
void WebGLRenderingContext : : depth_mask ( GLboolean mask )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::depth_mask(mask={}) " , mask ) ;
}
void WebGLRenderingContext : : depth_range ( GLclampf z_near , GLclampf z_far )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::depth_range(z_near={}, z_far={}) " , z_near , z_far ) ;
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#VIEWPORT_DEPTH_RANGE
// "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."
RETURN_WITH_WEBGL_ERROR_IF ( z_near > z_far , GL_INVALID_OPERATION ) ;
}
void WebGLRenderingContext : : finish ( )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::finish() " ) ;
}
void WebGLRenderingContext : : flush ( )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::flush() " ) ;
}
void WebGLRenderingContext : : front_face ( GLenum mode )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::front_face(mode={:#08x}) " , mode ) ;
}
GLenum WebGLRenderingContext : : get_error ( )
{
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::get_error() " ) ;
// "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."
// 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.
// The idea for the unconditional set is that any potentially error generating functions will not execute when the context is lost.
if ( m_error ! = GL_NO_ERROR | | m_context_lost ) {
auto last_error = m_error ;
m_error = GL_NO_ERROR ;
return last_error ;
}
2024-11-29 21:35:57 +01:00
return glGetError ( ) ;
2024-11-29 20:49:59 +01:00
}
void WebGLRenderingContext : : line_width ( GLfloat width )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::line_width(width={}) " , width ) ;
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#NAN_LINE_WIDTH
// "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."
RETURN_WITH_WEBGL_ERROR_IF ( isnan ( width ) , GL_INVALID_VALUE ) ;
}
void WebGLRenderingContext : : polygon_offset ( GLfloat factor , GLfloat units )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::polygon_offset(factor={}, units={}) " , factor , units ) ;
}
void WebGLRenderingContext : : scissor ( GLint x , GLint y , GLsizei width , GLsizei height )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::scissor(x={}, y={}, width={}, height={}) " , x , y , width , height ) ;
}
void WebGLRenderingContext : : stencil_op ( GLenum fail , GLenum zfail , GLenum zpass )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::stencil_op(fail={:#08x}, zfail={:#08x}, zpass={:#08x}) " , fail , zfail , zpass ) ;
}
void WebGLRenderingContext : : stencil_op_separate ( GLenum face , GLenum fail , GLenum zfail , GLenum zpass )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::stencil_op_separate(face={:#08x}, fail={:#08x}, zfail={:#08x}, zpass={:#08x}) " , face , fail , zfail , zpass ) ;
}
void WebGLRenderingContext : : viewport ( GLint x , GLint y , GLsizei width , GLsizei height )
{
if ( m_context_lost )
return ;
dbgln_if ( WEBGL_CONTEXT_DEBUG , " WebGLRenderingContext::viewport(x={}, y={}, width={}, height={}) " , x , y , width , height ) ;
}
2022-06-04 04:22:42 +01:00
}