use std::cell::UnsafeCell;
use std::hash::{Hash, Hasher};
use std::marker;
use std::sync::Arc;
use wasm_bindgen::JsCast;
use web_sys::WebGl2RenderingContext as Gl;
use crate::image::format::{Multisamplable, Multisample, RenderbufferFormat};
use crate::runtime::state::ContextUpdate;
use crate::runtime::{Connection, RenderingContext, UnsupportedSampleCount};
use crate::task::{ContextId, GpuTask, Progress};
use crate::util::JsId;
pub struct RenderbufferDescriptor<F> {
pub format: F,
pub width: u32,
pub height: u32,
}
pub struct Renderbuffer<F> {
object_id: u64,
data: Arc<RenderbufferData>,
_marker: marker::PhantomData<[F]>,
}
impl<F> Renderbuffer<F> {
pub(crate) fn data(&self) -> &Arc<RenderbufferData> {
&self.data
}
pub fn width(&self) -> u32 {
self.data.width
}
pub fn height(&self) -> u32 {
self.data.height
}
}
impl<F> Renderbuffer<Multisample<F>>
where
F: Multisamplable,
{
pub fn samples(&self) -> u8 {
self.data.samples.unwrap()
}
}
impl<F> Renderbuffer<F>
where
F: RenderbufferFormat + 'static,
{
pub(crate) fn new<Rc>(
context: &Rc,
object_id: u64,
descriptor: &RenderbufferDescriptor<F>,
) -> Self
where
Rc: RenderingContext + Clone + 'static,
{
let data = Arc::new(RenderbufferData {
id: UnsafeCell::new(None),
context_id: context.id(),
dropper: Box::new(context.clone()),
width: descriptor.width,
height: descriptor.height,
samples: None,
});
context.submit(RenderbufferAllocateCommand::<F> {
data: data.clone(),
_marker: marker::PhantomData,
});
Renderbuffer {
object_id,
data,
_marker: marker::PhantomData,
}
}
}
impl<F> Renderbuffer<Multisample<F>>
where
F: RenderbufferFormat + Multisamplable + Copy + 'static,
{
pub(crate) fn new_multisample<Rc>(
context: &Rc,
object_id: u64,
descriptor: &RenderbufferDescriptor<Multisample<F>>,
) -> Result<Self, UnsupportedSampleCount>
where
Rc: RenderingContext + Clone + 'static,
{
let supported_samples = context.supported_samples(descriptor.format.sample_format());
if !supported_samples
.into_iter()
.any(|count| count == descriptor.format.samples())
{
return Err(UnsupportedSampleCount {
supported_samples,
requested_samples: descriptor.format.samples(),
});
}
let data = Arc::new(RenderbufferData {
id: UnsafeCell::new(None),
context_id: context.id(),
dropper: Box::new(context.clone()),
width: descriptor.width,
height: descriptor.height,
samples: Some(descriptor.format.samples()),
});
context.submit(MultisampleRenderbufferAllocateCommand::<F> {
data: data.clone(),
samples: descriptor.format.samples(),
_marker: marker::PhantomData,
});
Ok(Renderbuffer {
object_id,
data,
_marker: marker::PhantomData,
})
}
}
impl<F> PartialEq for Renderbuffer<F> {
fn eq(&self, other: &Self) -> bool {
self.object_id == other.object_id
}
}
impl<F> Hash for Renderbuffer<F> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.object_id.hash(state);
}
}
trait RenderbufferObjectDropper {
fn drop_renderbuffer_object(&self, id: JsId);
}
impl<T> RenderbufferObjectDropper for T
where
T: RenderingContext,
{
fn drop_renderbuffer_object(&self, id: JsId) {
self.submit(RenderbufferDropCommand { id });
}
}
pub(crate) struct RenderbufferData {
id: UnsafeCell<Option<JsId>>,
context_id: u64,
dropper: Box<dyn RenderbufferObjectDropper>,
width: u32,
height: u32,
samples: Option<u8>,
}
impl RenderbufferData {
pub(crate) fn id(&self) -> Option<JsId> {
unsafe { *self.id.get() }
}
pub(crate) fn context_id(&self) -> u64 {
self.context_id
}
}
impl PartialEq for RenderbufferData {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Hash for RenderbufferData {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.id().hash(state);
}
}
impl Drop for RenderbufferData {
fn drop(&mut self) {
if let Some(id) = self.id() {
self.dropper.drop_renderbuffer_object(id);
}
}
}
struct RenderbufferAllocateCommand<F> {
data: Arc<RenderbufferData>,
_marker: marker::PhantomData<[F]>,
}
unsafe impl<F> GpuTask<Connection> for RenderbufferAllocateCommand<F>
where
F: RenderbufferFormat,
{
type Output = ();
fn context_id(&self) -> ContextId {
ContextId::Any
}
fn progress(&mut self, connection: &mut Connection) -> Progress<Self::Output> {
let (gl, state) = unsafe { connection.unpack_mut() };
let data = &self.data;
let object = gl.create_renderbuffer().unwrap();
state.bind_renderbuffer(Some(&object)).apply(gl).unwrap();
gl.renderbuffer_storage(
Gl::RENDERBUFFER,
F::ID,
data.width as i32,
data.height as i32,
);
unsafe {
*data.id.get() = Some(JsId::from_value(object.into()));
}
Progress::Finished(())
}
}
struct MultisampleRenderbufferAllocateCommand<F> {
data: Arc<RenderbufferData>,
samples: u8,
_marker: marker::PhantomData<[F]>,
}
unsafe impl<F> GpuTask<Connection> for MultisampleRenderbufferAllocateCommand<F>
where
F: RenderbufferFormat,
{
type Output = ();
fn context_id(&self) -> ContextId {
ContextId::Any
}
fn progress(&mut self, connection: &mut Connection) -> Progress<Self::Output> {
let (gl, state) = unsafe { connection.unpack_mut() };
let data = &self.data;
let object = gl.create_renderbuffer().unwrap();
state.bind_renderbuffer(Some(&object)).apply(gl).unwrap();
gl.renderbuffer_storage_multisample(
Gl::RENDERBUFFER,
self.samples as i32,
F::ID,
data.width as i32,
data.height as i32,
);
unsafe {
*data.id.get() = Some(JsId::from_value(object.into()));
}
Progress::Finished(())
}
}
struct RenderbufferDropCommand {
id: JsId,
}
unsafe impl GpuTask<Connection> for RenderbufferDropCommand {
type Output = ();
fn context_id(&self) -> ContextId {
ContextId::Any
}
fn progress(&mut self, connection: &mut Connection) -> Progress<Self::Output> {
let (gl, state) = unsafe { connection.unpack_mut() };
let value = unsafe { JsId::into_value(self.id).unchecked_into() };
state.unref_renderbuffer(&value);
gl.delete_renderbuffer(Some(&value));
Progress::Finished(())
}
}