use std::cell::Cell;
use dom_struct::dom_struct;
use script_bindings::weakref::WeakRef;
use servo_canvas_traits::webgl::{
GlType, InternalFormatIntVec, WebGLCommand, WebGLError, WebGLRenderbufferId, WebGLResult,
WebGLVersion, webgl_channel,
};
use crate::dom::bindings::codegen::Bindings::EXTColorBufferHalfFloatBinding::EXTColorBufferHalfFloatConstants;
use crate::dom::bindings::codegen::Bindings::WEBGLColorBufferFloatBinding::WEBGLColorBufferFloatConstants;
use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::webgl::webglframebuffer::WebGLFramebuffer;
use crate::dom::webgl::webglobject::WebGLObject;
use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
use crate::dom::webglrenderingcontext::capture_webgl_backtrace;
use crate::script_runtime::CanGc;
#[derive(JSTraceable, MallocSizeOf)]
struct DroppableWebGLRenderbuffer {
context: WeakRef<WebGLRenderingContext>,
#[no_trace]
id: WebGLRenderbufferId,
is_deleted: Cell<bool>,
}
impl DroppableWebGLRenderbuffer {
fn send_with_fallibility(&self, command: WebGLCommand, fallibility: Operation) {
if let Some(root) = self.context.root() {
let result = root.sender().send(command, capture_webgl_backtrace());
if matches!(fallibility, Operation::Infallible) {
result.expect("Operation failed");
}
}
}
fn delete(&self, operation_fallibility: Operation) {
if !self.is_deleted.get() {
self.is_deleted.set(true);
if let Some(context) = self.context.root() {
if let Some(fb) = context.get_draw_framebuffer_slot().get() {
let _ = fb.detach_renderbuffer_by_id(&self.id);
}
if let Some(fb) = context.get_read_framebuffer_slot().get() {
let _ = fb.detach_renderbuffer_by_id(&self.id);
}
}
self.send_with_fallibility(
WebGLCommand::DeleteRenderbuffer(self.id),
operation_fallibility,
);
}
}
}
impl Drop for DroppableWebGLRenderbuffer {
fn drop(&mut self) {
self.delete(Operation::Fallible);
}
}
#[dom_struct(associated_memory)]
pub(crate) struct WebGLRenderbuffer {
webgl_object: WebGLObject,
ever_bound: Cell<bool>,
size: Cell<Option<(i32, i32)>>,
internal_format: Cell<Option<u32>>,
is_initialized: Cell<bool>,
attached_framebuffer: MutNullableDom<WebGLFramebuffer>,
droppable: DroppableWebGLRenderbuffer,
}
impl WebGLRenderbuffer {
fn new_inherited(context: &WebGLRenderingContext, id: WebGLRenderbufferId) -> Self {
Self {
webgl_object: WebGLObject::new_inherited(context),
ever_bound: Cell::new(false),
internal_format: Cell::new(None),
size: Cell::new(None),
is_initialized: Cell::new(false),
attached_framebuffer: Default::default(),
droppable: DroppableWebGLRenderbuffer {
context: WeakRef::new(context),
id,
is_deleted: Cell::new(false),
},
}
}
pub(crate) fn maybe_new(context: &WebGLRenderingContext) -> Option<DomRoot<Self>> {
let (sender, receiver) = webgl_channel().unwrap();
context.send_command(WebGLCommand::CreateRenderbuffer(sender));
receiver
.recv()
.unwrap()
.map(|id| WebGLRenderbuffer::new(context, id, CanGc::deprecated_note()))
}
pub(crate) fn new(
context: &WebGLRenderingContext,
id: WebGLRenderbufferId,
can_gc: CanGc,
) -> DomRoot<Self> {
reflect_dom_object(
Box::new(WebGLRenderbuffer::new_inherited(context, id)),
&*context.global(),
can_gc,
)
}
}
impl WebGLRenderbuffer {
pub(crate) fn id(&self) -> WebGLRenderbufferId {
self.droppable.id
}
pub(crate) fn size(&self) -> Option<(i32, i32)> {
self.size.get()
}
pub(crate) fn internal_format(&self) -> u32 {
self.internal_format.get().unwrap_or(constants::RGBA4)
}
pub(crate) fn mark_initialized(&self) {
self.is_initialized.set(true);
}
pub(crate) fn is_initialized(&self) -> bool {
self.is_initialized.get()
}
pub(crate) fn bind(&self, target: u32) {
self.ever_bound.set(true);
self.upcast()
.send_command(WebGLCommand::BindRenderbuffer(target, Some(self.id())));
}
pub(crate) fn delete(&self, operation_fallibility: Operation) {
self.droppable.delete(operation_fallibility);
}
pub(crate) fn is_deleted(&self) -> bool {
self.droppable.is_deleted.get()
}
pub(crate) fn ever_bound(&self) -> bool {
self.ever_bound.get()
}
pub(crate) fn storage(
&self,
api_type: GlType,
sample_count: i32,
internal_format: u32,
width: i32,
height: i32,
) -> WebGLResult<()> {
let Some(context) = self.upcast().context() else {
return Err(WebGLError::ContextLost);
};
let webgl_version = context.webgl_version();
let is_gles = api_type == GlType::Gles;
let actual_format = match internal_format {
constants::RGBA4 | constants::DEPTH_COMPONENT16 | constants::STENCIL_INDEX8 => {
internal_format
},
constants::R8 |
constants::R8UI |
constants::R8I |
constants::R16UI |
constants::R16I |
constants::R32UI |
constants::R32I |
constants::RG8 |
constants::RG8UI |
constants::RG8I |
constants::RG16UI |
constants::RG16I |
constants::RG32UI |
constants::RG32I |
constants::RGB8 |
constants::RGBA8 |
constants::SRGB8_ALPHA8 |
constants::RGB10_A2 |
constants::RGBA8UI |
constants::RGBA8I |
constants::RGB10_A2UI |
constants::RGBA16UI |
constants::RGBA16I |
constants::RGBA32I |
constants::RGBA32UI |
constants::DEPTH_COMPONENT24 |
constants::DEPTH_COMPONENT32F |
constants::DEPTH24_STENCIL8 |
constants::DEPTH32F_STENCIL8 => match webgl_version {
WebGLVersion::WebGL1 => return Err(WebGLError::InvalidEnum),
_ => internal_format,
},
constants::DEPTH_STENCIL => constants::DEPTH24_STENCIL8,
constants::RGB5_A1 => {
if is_gles {
constants::RGB5_A1
} else {
constants::RGBA8
}
},
constants::RGB565 => {
if is_gles {
constants::RGB565
} else {
constants::RGB8
}
},
EXTColorBufferHalfFloatConstants::RGBA16F_EXT |
EXTColorBufferHalfFloatConstants::RGB16F_EXT => {
if !context
.extension_manager()
.is_half_float_buffer_renderable()
{
return Err(WebGLError::InvalidEnum);
}
internal_format
},
WEBGLColorBufferFloatConstants::RGBA32F_EXT => {
if !context.extension_manager().is_float_buffer_renderable() {
return Err(WebGLError::InvalidEnum);
}
internal_format
},
_ => return Err(WebGLError::InvalidEnum),
};
if webgl_version != WebGLVersion::WebGL1 {
let (sender, receiver) = webgl_channel().unwrap();
self.upcast()
.send_command(WebGLCommand::GetInternalFormatIntVec(
constants::RENDERBUFFER,
internal_format,
InternalFormatIntVec::Samples,
sender,
));
let samples = receiver.recv().unwrap();
if sample_count < 0 || sample_count > samples.first().cloned().unwrap_or(0) {
return Err(WebGLError::InvalidOperation);
}
}
self.internal_format.set(Some(internal_format));
self.is_initialized.set(false);
if let Some(fb) = self.attached_framebuffer.get() {
fb.update_status();
}
let command = match sample_count {
0 => WebGLCommand::RenderbufferStorage(
constants::RENDERBUFFER,
actual_format,
width,
height,
),
_ => WebGLCommand::RenderbufferStorageMultisample(
constants::RENDERBUFFER,
sample_count,
actual_format,
width,
height,
),
};
self.upcast().send_command(command);
self.size.set(Some((width, height)));
Ok(())
}
pub(crate) fn attach_to_framebuffer(&self, fb: &WebGLFramebuffer) {
self.attached_framebuffer.set(Some(fb));
}
pub(crate) fn detach_from_framebuffer(&self) {
self.attached_framebuffer.set(None);
}
}