Refactor GLES context management

Changes the GLES contexts so that host contexts cannot be used until
activated, which hands out a helper object that ensures the provided
context is the only active context.

Change-Id: I8519665ce8854c848771b794a5b3416517ff7e3f
This commit is contained in:
abnormalmaps
2025-03-09 19:26:50 -04:00
parent 15128d42d5
commit 73cf9b27dc
9 changed files with 427 additions and 257 deletions

View File

@@ -18,7 +18,7 @@ use crate::frameworks::core_graphics::{cg_bitmap_context, cg_image, CGFloat, CGR
use crate::gles::gles11_raw as gles11; // constants only
use crate::gles::gles11_raw::types::*;
use crate::gles::present::{present_frame, FpsCounter};
use crate::gles::GLES;
use crate::gles::GLES; // constants only
use crate::image::Image;
use crate::matrix::Matrix;
use crate::mem::SafeWrite;
@@ -150,8 +150,7 @@ pub fn recomposite_if_necessary(env: &mut Environment, force: bool) -> Option<In
let opacity = 1.0;
let window = env.window.as_mut().unwrap();
window.make_internal_gl_ctx_current();
let gles = window.get_internal_gl_ctx();
let mut gles = window.make_internal_gl_ctx_current();
// Set up GL objects needed for render-to-texture. We could draw directly
// to the screen instead, but this way we can reuse the code for scaling and
@@ -240,7 +239,7 @@ pub fn recomposite_if_necessary(env: &mut Environment, force: bool) -> Option<In
gles11::GENERATE_MIPMAP,
gles11::TRUE as _,
);
upload_rgba8_pixels(gles, image.pixels(), (dimension as _, dimension as _));
upload_rgba8_pixels(gles.as_mut(), image.pixels(), (dimension as _, dimension as _));
gles.TexParameteri(
gles11::TEXTURE_2D,
gles11::TEXTURE_MIN_FILTER,
@@ -265,14 +264,14 @@ pub fn recomposite_if_necessary(env: &mut Environment, force: bool) -> Option<In
};
unsafe {
gles.BindBuffer(gles11::ARRAY_BUFFER, basic_square_buffer);
upload_slice(gles, gles11::ARRAY_BUFFER, &BASIC_SQUARE_POINTS, gles11::STATIC_DRAW);
upload_slice(gles.as_mut(), gles11::ARRAY_BUFFER, &BASIC_SQUARE_POINTS, gles11::STATIC_DRAW);
gles.BindBuffer(gles11::ARRAY_BUFFER, flipped_square_buffer);
upload_slice(gles, gles11::ARRAY_BUFFER, &FLIPPED_SQUARE_POINTS, gles11::STATIC_DRAW);
upload_slice(gles.as_mut(), gles11::ARRAY_BUFFER, &FLIPPED_SQUARE_POINTS, gles11::STATIC_DRAW);
gles.BindBuffer(gles11::ARRAY_BUFFER, rounded_vertex_buffer);
upload_slice(gles, gles11::ARRAY_BUFFER, &[0f32; FLOATS_PER_9PATCH], gles11::DYNAMIC_DRAW);
upload_slice(gles.as_mut(), gles11::ARRAY_BUFFER, &[0f32; FLOATS_PER_9PATCH], gles11::DYNAMIC_DRAW);
gles.BindBuffer(gles11::ARRAY_BUFFER, rounded_tex_coord_buffer);
upload_slice(
gles,
gles.as_mut(),
gles11::ARRAY_BUFFER,
&make_9patch_coords([0.0, 1.0, 1.0, 0.0], [0.0, 1.0, 1.0, 0.0]),
gles11::STATIC_DRAW,
@@ -281,7 +280,7 @@ pub fn recomposite_if_necessary(env: &mut Environment, force: bool) -> Option<In
gles.BindBuffer(gles11::ARRAY_BUFFER, 0);
gles.BindBuffer(gles11::ELEMENT_ARRAY_BUFFER, index_buffer);
upload_slice(gles, gles11::ELEMENT_ARRAY_BUFFER, &make_9patch_indices(), gles11::STATIC_DRAW);
upload_slice(gles.as_mut(), gles11::ELEMENT_ARRAY_BUFFER, &make_9patch_indices(), gles11::STATIC_DRAW);
// Prevent accidental subsequent use.
gles.BindBuffer(gles11::ELEMENT_ARRAY_BUFFER, 0);
}
@@ -310,7 +309,7 @@ pub fn recomposite_if_necessary(env: &mut Environment, force: bool) -> Option<In
// Using the projection matrix for this is more convenient than adding
// an extra multiply to composite_layer_recursive.
load_matrix(
gles,
gles.as_mut(),
Matrix::from(&Matrix::scale_2d(
2.0 / screen_bounds.size.width,
-2.0 / screen_bounds.size.height,
@@ -323,6 +322,7 @@ pub fn recomposite_if_necessary(env: &mut Environment, force: bool) -> Option<In
// One index buffer to rule them all
gles.BindBuffer(gles11::ELEMENT_ARRAY_BUFFER, misc_gl_objects.index_buffer);
}
std::mem::drop(gles);
// Assumes the windows in the list are ordered back-to-front.
// TODO: this may not be correct once we support windowLevel.
@@ -341,7 +341,7 @@ pub fn recomposite_if_necessary(env: &mut Environment, force: bool) -> Option<In
// Re-borrow
let window = env.window.as_mut().unwrap();
let gles = window.get_internal_gl_ctx();
let mut gles = window.make_internal_gl_ctx_current();
// Clean up some GL state
unsafe {
@@ -363,13 +363,14 @@ pub fn recomposite_if_necessary(env: &mut Environment, force: bool) -> Option<In
gles.BindTexture(gles11::TEXTURE_2D, texture);
gles.BindFramebufferOES(gles11::FRAMEBUFFER_OES, 0);
present_frame(
gles,
gles.as_mut(),
present_frame_args.0,
present_frame_args.1,
present_frame_args.2,
);
}
env.window().swap_window();
std::mem::drop(gles);
window.swap_window();
animation_state.update_started_and_finished_animations(env);
@@ -423,7 +424,7 @@ unsafe fn composite_layer_recursive(
}
let window = env.window.as_mut().unwrap();
let gles = window.get_internal_gl_ctx();
let mut gles = window.make_internal_gl_ctx_current();
let opacity = opacity * host_obj.opacity;
let cumulative_transform = {
@@ -438,7 +439,7 @@ unsafe fn composite_layer_recursive(
// so it will have the right size in this layer's co-ordinate space.
gles.MatrixMode(gles11::MODELVIEW);
load_matrix(
gles,
gles.as_mut(),
Matrix::<4>::from(&Matrix::scale_2d(bounds.size.width, bounds.size.height))
.multiply(&Matrix::translate_3d(bounds.origin.x, bounds.origin.y, 0.0))
.multiply(&cumulative_transform),
@@ -492,7 +493,7 @@ unsafe fn composite_layer_recursive(
gles.EnableClientState(gles11::VERTEX_ARRAY);
gles.BindBuffer(gles11::ARRAY_BUFFER, misc.rounded_vertex_buffer);
upload_slice(
gles,
gles.as_mut(),
gles11::ARRAY_BUFFER,
&make_9patch_coords(
[
@@ -558,7 +559,7 @@ unsafe fn composite_layer_recursive(
}
}
upload_rgba8_pixels(gles, pixels, (width, height));
upload_rgba8_pixels(gles.as_mut(), pixels, (width, height));
}
}
@@ -569,14 +570,14 @@ unsafe fn composite_layer_recursive(
// No special handling for opacity is needed here: the alpha channel
// on an image is meaningful and won't be ignored.
upload_rgba8_pixels(gles, image.pixels(), image.dimensions());
upload_rgba8_pixels(gles.as_mut(), image.pixels(), image.dimensions());
} else if let Some(cg_context) = host_obj.cg_context {
// Make sure this is in sync with the code in ca_layer.rs that
// sets up the context!
let (width, height, data) = cg_bitmap_context::get_data(&env.objc, cg_context);
let size = width * height * 4;
let pixels = env.mem.bytes_at(data.cast(), size);
upload_rgba8_pixels(gles, pixels, (width, height));
upload_rgba8_pixels(gles.as_mut(), pixels, (width, height));
}
}
@@ -629,6 +630,7 @@ unsafe fn composite_layer_recursive(
0 as *const GLvoid,
);
}
std::mem::drop(gles);
// avoid holding mutable borrow while recursing
let original_host_obj = env.objc.borrow_mut::<CALayerHostObject>(layer);

View File

@@ -28,8 +28,6 @@ pub const DYLIB: crate::dyld::HostDylib = crate::dyld::HostDylib {
pub struct State {
/// Current EAGLContext for each thread
current_ctxs: std::collections::HashMap<crate::ThreadId, Option<crate::objc::id>>,
/// Which thread's EAGLContext is currently active
current_ctx_thread: Option<crate::ThreadId>,
strings_cache: std::collections::HashMap<GLenum, ConstPtr<u8>>,
}
impl State {
@@ -39,24 +37,23 @@ impl State {
}
}
fn sync_context<'a>(
fn sync_context<'objc, 'win: 'objc>(
state: &mut State,
objc: &'a mut crate::objc::ObjC,
window: &mut crate::window::Window,
objc: &'objc mut crate::objc::ObjC,
window: &'win mut crate::window::Window,
current_thread: crate::ThreadId,
) -> &'a mut dyn crate::gles::GLES {
) -> Box<dyn crate::gles::GLES + 'objc> {
let gles_ctx = get_thread_context(state, objc, current_thread);
gles_ctx.make_current(window)
}
fn get_thread_context<'objc>(
state: &mut State,
objc: &'objc mut crate::objc::ObjC,
current_thread: crate::ThreadId,
) -> &'objc mut dyn crate::gles::GLESContext {
let current_ctx = state.current_ctx_for_thread(current_thread);
let host_obj = objc.borrow_mut::<eagl::EAGLContextHostObject>(current_ctx.unwrap());
let gles_ctx = host_obj.gles_ctx.as_deref_mut().unwrap();
if window.is_app_gl_ctx_no_longer_current() || state.current_ctx_thread != Some(current_thread)
{
log_dbg!(
"Restoring guest app OpenGL context for thread {}.",
current_thread
);
gles_ctx.make_current(window);
}
gles_ctx
}

View File

@@ -15,11 +15,11 @@ use crate::frameworks::foundation::NSUInteger;
use crate::gles::gles11_raw as gles11; // constants only
use crate::gles::gles11_raw::types::*;
use crate::gles::present::{present_frame, FpsCounter};
use crate::gles::{create_gles1_ctx, gles1_on_gl2, GLES};
use crate::gles::{create_gles1_ctx, gles1_on_gl2, GLESContext, GLES};
use crate::mem::MutPtr;
use crate::objc::{id, msg, nil, objc_classes, release, retain, ClassExports, HostObject};
use crate::options::Options;
use crate::window::Window;
use crate::Environment;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
@@ -60,7 +60,7 @@ const kEAGLRenderingAPIOpenGLES2: EAGLRenderingAPI = 2;
const kEAGLRenderingAPIOpenGLES3: EAGLRenderingAPI = 3;
pub(super) struct EAGLContextHostObject {
pub(super) gles_ctx: Option<Box<dyn GLES>>,
pub(super) gles_ctx: Option<Box<dyn GLESContext>>,
/// Mapping of OpenGL ES renderbuffer names to `EAGLDrawable` instances
/// (always `CAEAGLLayer*`). Retains the instance so it won't dangle.
renderbuffer_drawable_bindings: Rc<RefCell<HashMap<GLuint, id>>>,
@@ -93,24 +93,17 @@ pub const CLASSES: ClassExports = objc_classes! {
+ (bool)setCurrentContext:(id)context { // EAGLContext*
retain(env, context);
// Clear flag value, we're changing context anyway.
let _ = env.window_mut().is_app_gl_ctx_no_longer_current();
let current_ctx = env.framework_state.opengles.current_ctx_for_thread(env.current_thread);
if let Some(old_ctx) = std::mem::take(current_ctx) {
release(env, old_ctx);
env.framework_state.opengles.current_ctx_thread = None;
}
// reborrow
let current_ctx = env.framework_state.opengles.current_ctx_for_thread(env.current_thread);
if context != nil {
let host_obj = env.objc.borrow_mut::<EAGLContextHostObject>(context);
host_obj.gles_ctx.as_mut().unwrap().make_current(env.window.as_ref().unwrap());
*current_ctx = Some(context);
env.framework_state.opengles.current_ctx_thread = Some(env.current_thread);
}
true
@@ -131,20 +124,31 @@ pub const CLASSES: ClassExports = objc_classes! {
}
let window = env.window.as_mut().expect("OpenGL ES is not supported in headless mode");
let prev_context = env.objc.borrow::<EAGLContextHostObject>(group).gles_ctx.as_ref().unwrap();
prev_context.make_current(window);
let prev_context = env.objc.borrow_mut::<EAGLContextHostObject>(group).gles_ctx.as_mut().unwrap();
// This is sort of a hack - we set the "current" context, then immediately
// drop it. Since we know all the code between here and creating the new
// context, we know that there won't be any context switches, so it's fine
// to do this.
{
let _prev_ctx = prev_context.make_current(window);
}
env.window.as_mut().unwrap().set_share_with_current_context(true);
let res: id = msg![env; this initWithAPI:api];
// Setting current_ctx_thread to None should cause sync_context to
// switch back to the right context if the app makes an OpenGL ES call.
// (it's already done in initWithAPI: but we want to be explicit here.)
env.framework_state.opengles.current_ctx_thread = None;
let window = env.window.as_mut().expect("OpenGL ES is not supported in headless mode");
let mut gles1_ins = create_gles1_ctx(window, &env.options);
{
let gles1_ctx = gles1_ins.make_current(window);
log!("Driver info: {}", unsafe { gles1_ctx.driver_description() });
}
env.objc.borrow_mut::<EAGLContextHostObject>(this).gles_ctx = Some(gles1_ins);
env.window.as_mut().unwrap().set_share_with_current_context(false);
env.objc.borrow_mut::<EAGLContextHostObject>(res).renderbuffer_drawable_bindings = env.objc.borrow::<EAGLContextHostObject>(group).renderbuffer_drawable_bindings.clone();
res
env.objc.borrow_mut::<EAGLContextHostObject>(this).renderbuffer_drawable_bindings = env.objc.borrow::<EAGLContextHostObject>(group).renderbuffer_drawable_bindings.clone();
this
}
- (id)initWithAPI:(EAGLRenderingAPI)api {
@@ -157,18 +161,14 @@ pub const CLASSES: ClassExports = objc_classes! {
}
let window = env.window.as_mut().expect("OpenGL ES is not supported in headless mode");
let gles1_ctx = create_gles1_ctx(window, &env.options);
let mut gles1_ins = create_gles1_ctx(window, &env.options);
// Make the context current so we can get driver info from it.
// initWithAPI: is not supposed to make the new context current (the app
// must call setCurrentContext: for that), so we need to hide this from the
// app. Setting current_ctx_thread to None should cause sync_context to
// switch back to the right context if the app makes an OpenGL ES call.
gles1_ctx.make_current(window);
env.framework_state.opengles.current_ctx_thread = None;
log!("Driver info: {}", unsafe { gles1_ctx.driver_description() });
{
let gles1_ctx = gles1_ins.make_current(window);
log!("Driver info: {}", unsafe { gles1_ctx.driver_description() });
}
env.objc.borrow_mut::<EAGLContextHostObject>(this).gles_ctx = Some(gles1_ctx);
env.objc.borrow_mut::<EAGLContextHostObject>(this).gles_ctx = Some(gles1_ins);
this
}
@@ -235,14 +235,17 @@ pub const CLASSES: ClassExports = objc_classes! {
let window = env.window.as_mut().expect("OpenGL ES is not supported in headless mode");
// Unclear from documentation if this method requires an appropriate context
// to already be active, but that seems to be the case in practice?
let gles = super::sync_context(&mut env.framework_state.opengles, &mut env.objc, window, env.current_thread);
let renderbuffer: GLuint = unsafe {
gles.RenderbufferStorageOES(target, internalformat, width.try_into().unwrap(), height.try_into().unwrap());
let mut renderbuffer = 0;
gles.GetIntegerv(gles11::RENDERBUFFER_BINDING_OES, &mut renderbuffer);
renderbuffer as _
let renderbuffer = {
// Unclear from documentation if this method requires an appropriate
// context to already be active, but that seems to be the case
// in practice?
let mut gles = super::sync_context(&mut env.framework_state.opengles, &mut env.objc, window, env.current_thread);
unsafe {
gles.RenderbufferStorageOES(target, internalformat, width.try_into().unwrap(), height.try_into().unwrap());
let mut renderbuffer = 0;
gles.GetIntegerv(gles11::RENDERBUFFER_BINDING_OES, &mut renderbuffer);
renderbuffer as _
}
};
retain(env, drawable);
@@ -279,7 +282,7 @@ pub const CLASSES: ClassExports = objc_classes! {
// Unclear from documentation if this method requires the context to be
// current, but it would be weird if it didn't?
let window = env.window.as_mut().expect("OpenGL ES is not supported in headless mode");
let gles = super::sync_context(&mut env.framework_state.opengles, &mut env.objc, window, env.current_thread);
let mut gles = super::sync_context(&mut env.framework_state.opengles, &mut env.objc, window, env.current_thread);
let renderbuffer: GLuint = unsafe {
let mut renderbuffer = 0;
@@ -287,6 +290,8 @@ pub const CLASSES: ClassExports = objc_classes! {
renderbuffer as _
};
std::mem::drop(gles);
let Some(&drawable) = env
.objc
.borrow::<EAGLContextHostObject>(this)
@@ -306,9 +311,8 @@ pub const CLASSES: ClassExports = objc_classes! {
renderbuffer,
);
// re-borrow
let gles = super::sync_context(&mut env.framework_state.opengles, &mut env.objc, env.window.as_mut().unwrap(), env.current_thread);
unsafe {
present_renderbuffer(gles, env.window.as_mut().unwrap());
present_renderbuffer(env);
}
} else {
if fullscreen_layer != nil {
@@ -340,9 +344,11 @@ pub const CLASSES: ClassExports = objc_classes! {
);
let pixels_vec = get_pixels_vec_for_presenting(env, drawable);
// re-borrow
let gles = super::sync_context(&mut env.framework_state.opengles, &mut env.objc, env.window.as_mut().unwrap(), env.current_thread);
let (pixels_vec, width, height) = unsafe {
read_renderbuffer(gles, pixels_vec)
let (pixels_vec, width, height) = {
let mut gles = super::sync_context(&mut env.framework_state.opengles, &mut env.objc, env.window.as_mut().unwrap(), env.current_thread);
unsafe {
read_renderbuffer(gles.as_mut(), pixels_vec)
}
};
present_pixels(env, drawable, pixels_vec, width, height);
}
@@ -541,9 +547,21 @@ unsafe fn read_renderbuffer(gles: &mut dyn GLES, mut pixel_buffer: Vec<u8>) -> (
/// (which should be provided by the app) to a texture and presents it with
/// [present_frame], trying to avoid noticeably modifying OpenGL ES state while
/// doing so. The front and back buffers are then swapped.
///
/// The provided context must be current.
unsafe fn present_renderbuffer(gles: &mut dyn GLES, window: &mut Window) {
unsafe fn present_renderbuffer(env: &mut Environment) {
// Save these for when we need to draw the frame
let viewport = env.window.as_mut().unwrap().viewport();
let rotation_matrix = env.window.as_mut().unwrap().rotation_matrix();
let virtual_cursor_visible_at = env.window.as_mut().unwrap().virtual_cursor_visible_at();
let gles_ctx = super::get_thread_context(
&mut env.framework_state.opengles,
&mut env.objc,
env.current_thread,
);
let mut gles_boxed = gles_ctx.make_current(env.window.as_mut().unwrap());
let gles = gles_boxed.as_mut();
// We can't directly copy the content of the renderbuffer to the default
// framebuffer (the window), but if we attach it to a framebuffer object, we
// can use glCopyTexImage2D() to copy it to a texture, which we can then
@@ -661,12 +679,7 @@ unsafe fn present_renderbuffer(gles: &mut dyn GLES, window: &mut Window) {
);
// Draw the quad
present_frame(
gles,
window.viewport(),
window.rotation_matrix(),
window.virtual_cursor_visible_at(),
);
present_frame(gles, viewport, rotation_matrix, virtual_cursor_visible_at);
// Clean up the texture
gles.DeleteTextures(1, &texture);
@@ -733,13 +746,18 @@ unsafe fn present_renderbuffer(gles: &mut dyn GLES, window: &mut Window) {
old_tex_env_mode_arr.as_ptr().cast(),
);
std::mem::drop(gles_boxed);
// SDL2's documentation warns 0 should be bound to the draw framebuffer
// when swapping the window, so this is the perfect moment.
window.swap_window();
env.window.as_ref().unwrap().swap_window();
let mut gles_boxed = gles_ctx.make_current(env.window.as_mut().unwrap());
let gles = gles_boxed.as_mut();
// Restore the other bindings
gles.BindTexture(gles11::TEXTURE_2D, old_texture_2d);
gles.BindFramebufferOES(gles11::FRAMEBUFFER_OES, old_framebuffer);
//{ let err = gl21::GetError(); if err != 0 { panic!("{:#x}", err); } }
// { let err = gles.GetError(); if err != 0 { panic!("{:#x}", err); } }
}

View File

@@ -19,8 +19,7 @@ use touchHLE_gl_bindings::gles11::{
use crate::dyld::{export_c_func, FunctionExports};
use crate::frameworks::opengles::eagl::EAGLContextHostObject;
use crate::gles::gles11_raw as gles11; // constants only
use crate::gles::GLES;
use crate::gles::{gles11_raw as gles11, GLES}; // constants only
use crate::mem::{ConstPtr, ConstVoidPtr, GuestISize, GuestUSize, Mem, MutPtr, MutVoidPtr, Ptr};
use crate::objc::nil;
use crate::Environment;
@@ -79,7 +78,7 @@ where
return U::default();
}
let gles = super::sync_context(
let mut gles = super::sync_context(
&mut env.framework_state.opengles,
&mut env.objc,
env.window
@@ -89,7 +88,7 @@ where
);
//panic_on_gl_errors(&mut *gles);
let res = f(gles, &mut env.mem);
let res = f(gles.as_mut(), &mut env.mem);
//panic_on_gl_errors(&mut *gles);
#[allow(clippy::let_and_return)]
res
@@ -104,7 +103,7 @@ fn with_ctx_and_mem_no_skip<T, U>(env: &mut Environment, f: T) -> U
where
T: FnOnce(&mut dyn GLES, &mut Mem) -> U,
{
let gles = super::sync_context(
let mut gles = super::sync_context(
&mut env.framework_state.opengles,
&mut env.objc,
env.window
@@ -113,9 +112,9 @@ where
env.current_thread,
);
//panic_on_gl_errors(&mut *gles);
let res = f(gles, &mut env.mem);
//panic_on_gl_errors(&mut *gles);
//panic_on_gl_errors(&mut **gles);
let res = f(gles.as_mut(), &mut env.mem);
//panic_on_gl_errors(&mut **gles);
#[allow(clippy::let_and_return)]
res
}

View File

@@ -72,16 +72,17 @@ mod util;
use touchHLE_gl_bindings::gl21compat as gl21compat_raw;
pub use touchHLE_gl_bindings::gles11 as gles11_raw;
use gles1_native::GLES1Native;
use gles1_on_gl2::GLES1OnGL2;
use gles1_native::GLES1NativeContext;
use gles1_on_gl2::GLES1OnGL2Context;
pub use gles_generic::GLESContext;
pub use gles_generic::GLES;
/// Labels for [GLES] implementations and an abstraction for constructing them.
#[derive(Copy, Clone)]
pub enum GLESImplementation {
/// [GLES1Native].
/// [gles1_native::GLES1Native].
GLES1Native,
/// [GLES1OnGL2].
/// [gles1_on_gl2::GLES1OnGL2].
GLES1OnGL2,
}
impl GLESImplementation {
@@ -96,21 +97,24 @@ impl GLESImplementation {
_ => Err(()),
}
}
/// See [GLES::description].
/// See [GLESContext::description].
pub fn description(self) -> &'static str {
match self {
Self::GLES1Native => GLES1Native::description(),
Self::GLES1OnGL2 => GLES1OnGL2::description(),
Self::GLES1Native => GLES1NativeContext::description(),
Self::GLES1OnGL2 => GLES1OnGL2Context::description(),
}
}
/// See [GLES::new].
pub fn construct(self, window: &mut crate::window::Window) -> Result<Box<dyn GLES>, String> {
fn boxer<T: GLES + 'static>(ctx: T) -> Box<dyn GLES> {
/// See [GLESContext::new].
pub fn construct(
self,
window: &mut crate::window::Window,
) -> Result<Box<dyn GLESContext>, String> {
fn boxer<T: GLESContext + 'static>(ctx: T) -> Box<dyn GLESContext> {
Box::new(ctx)
}
match self {
Self::GLES1Native => GLES1Native::new(window).map(boxer),
Self::GLES1OnGL2 => GLES1OnGL2::new(window).map(boxer),
Self::GLES1Native => GLES1NativeContext::new(window).map(boxer),
Self::GLES1OnGL2 => GLES1OnGL2Context::new(window).map(boxer),
}
}
}
@@ -120,7 +124,7 @@ impl GLESImplementation {
pub fn create_gles1_ctx(
window: &mut crate::window::Window,
options: &crate::options::Options,
) -> Box<dyn GLES> {
) -> Box<dyn GLESContext> {
log!("Creating an OpenGL ES 1.1 context:");
let list = if let Some(ref preference) = options.gles1_implementation {
std::slice::from_ref(preference)

View File

@@ -14,15 +14,18 @@
use super::gles11_raw as gles11;
use super::gles11_raw::types::*;
use super::gles_generic::GLES;
use super::util::{try_decode_pvrtc, PalettedTextureFormat};
use super::GLES;
use super::GLESContext;
use crate::window::{GLContext, GLVersion, Window};
use std::ffi::CStr;
use std::marker::PhantomData;
pub struct GLES1Native {
pub struct GLES1NativeContext {
gl_ctx: GLContext,
is_loaded: bool,
}
impl GLES for GLES1Native {
impl GLESContext for GLES1NativeContext {
fn description() -> &'static str {
"Native OpenGL ES 1.1"
}
@@ -30,14 +33,55 @@ impl GLES for GLES1Native {
fn new(window: &mut Window) -> Result<Self, String> {
Ok(Self {
gl_ctx: window.create_gl_context(GLVersion::GLES11)?,
is_loaded: false,
})
}
fn make_current(&self, window: &Window) {
unsafe { window.make_gl_context_current(&self.gl_ctx) };
gles11::load_with(|s| window.gl_get_proc_address(s))
fn make_current<'gl_ctx, 'win: 'gl_ctx>(
&'gl_ctx mut self,
window: &'win mut Window,
) -> Box<dyn GLES + 'gl_ctx> {
if self.gl_ctx.is_current() && self.is_loaded {
return Box::new(GLES1Native {
_gl_lifetime: PhantomData,
});
}
unsafe {
window.make_gl_context_current(&self.gl_ctx);
}
gles11::load_with(|s| window.gl_get_proc_address(s));
self.is_loaded = true;
Box::new(GLES1Native {
_gl_lifetime: PhantomData,
})
}
unsafe fn make_current_unchecked_for_window<'gl_ctx>(
&'gl_ctx mut self,
make_current_fn: &mut dyn FnMut(&GLContext),
loader_fn: &mut dyn FnMut(&'static str) -> *const std::ffi::c_void,
) -> Box<dyn GLES + 'gl_ctx> {
if self.gl_ctx.is_current() && self.is_loaded {
return Box::new(GLES1Native {
_gl_lifetime: PhantomData,
});
}
make_current_fn(&self.gl_ctx);
gles11::load_with(loader_fn);
self.is_loaded = true;
Box::new(GLES1Native {
_gl_lifetime: PhantomData,
})
}
}
pub struct GLES1Native<'gl_ctx> {
_gl_lifetime: PhantomData<&'gl_ctx ()>,
}
impl GLES for GLES1Native<'_> {
unsafe fn driver_description(&self) -> String {
let version = CStr::from_ptr(gles11::GetString(gles11::VERSION) as *const _);
let vendor = CStr::from_ptr(gles11::GetString(gles11::VENDOR) as *const _);

View File

@@ -21,11 +21,12 @@
use super::gl21compat_raw as gl21;
use super::gl21compat_raw::types::*;
use super::gles11_raw as gles11; // constants only
use super::gles_generic::GLES;
use super::util::{
fixed_to_float, matrix_fixed_to_float, try_decode_pvrtc, PalettedTextureFormat, ParamTable,
ParamType,
};
use super::GLES;
use super::GLESContext;
use crate::window::{GLContext, GLVersion, Window};
use std::collections::HashSet;
use std::ffi::CStr;
@@ -383,13 +384,81 @@ const TEX_PARAMS: ParamTable = ParamTable(&[
(gl21::MAX_TEXTURE_MAX_ANISOTROPY_EXT, ParamType::Float, 1),
]);
pub struct GLES1OnGL2 {
gl_ctx: GLContext,
pub struct GLES1OnGL2State {
pointer_is_fixed_point: [bool; ARRAYS.len()],
fixed_point_texture_units: HashSet<GLenum>,
fixed_point_translation_buffers: [Vec<GLfloat>; ARRAYS.len()],
}
impl GLES1OnGL2 {
pub struct GLES1OnGL2Context {
gl_ctx: GLContext,
state: GLES1OnGL2State,
is_loaded: bool,
}
impl GLESContext for GLES1OnGL2Context {
fn description() -> &'static str {
"OpenGL ES 1.1 via touchHLE GLES1-on-GL2 layer"
}
fn new(window: &mut Window) -> Result<Self, String> {
Ok(Self {
gl_ctx: window.create_gl_context(GLVersion::GL21Compat)?,
state: GLES1OnGL2State {
pointer_is_fixed_point: [false; ARRAYS.len()],
fixed_point_texture_units: HashSet::new(),
fixed_point_translation_buffers: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],
},
is_loaded: false,
})
}
fn make_current<'gl_ctx, 'win: 'gl_ctx>(
&'gl_ctx mut self,
window: &'win mut Window,
) -> Box<dyn GLES + 'gl_ctx> {
if self.gl_ctx.is_current() && self.is_loaded {
return Box::new(GLES1OnGL2 {
state: &mut self.state,
});
}
unsafe {
window.make_gl_context_current(&self.gl_ctx);
}
gl21::load_with(|s| window.gl_get_proc_address(s));
self.is_loaded = true;
Box::new(GLES1OnGL2 {
state: &mut self.state,
})
}
unsafe fn make_current_unchecked_for_window<'gl_ctx>(
&'gl_ctx mut self,
make_current_fn: &mut dyn FnMut(&GLContext),
loader_fn: &mut dyn FnMut(&'static str) -> *const std::ffi::c_void,
) -> Box<dyn GLES + 'gl_ctx> {
if self.gl_ctx.is_current() && self.is_loaded {
return Box::new(GLES1OnGL2 {
state: &mut self.state,
});
}
make_current_fn(&self.gl_ctx);
gl21::load_with(loader_fn);
self.is_loaded = true;
Box::new(GLES1OnGL2 {
state: &mut self.state,
})
}
}
pub struct GLES1OnGL2<'a> {
state: &'a mut GLES1OnGL2State,
}
impl GLES1OnGL2<'_> {
/// If any arrays with fixed-point data are in use at the time of a draw
/// call, this function will convert the data to floating-point and
/// replace the pointers. [Self::restore_fixed_point_arrays] can be called
@@ -403,7 +472,7 @@ impl GLES1OnGL2 {
for (i, array_info) in ARRAYS.iter().enumerate() {
// Decide whether we need to do anything for this array
if !self.pointer_is_fixed_point[i] {
if !self.state.pointer_is_fixed_point[i] {
continue;
}
@@ -416,7 +485,11 @@ impl GLES1OnGL2 {
gl21::ACTIVE_TEXTURE,
&mut active_texture as *mut _ as *mut _,
);
if !self.fixed_point_texture_units.contains(&active_texture) {
if !self
.state
.fixed_point_texture_units
.contains(&active_texture)
{
continue;
}
@@ -489,7 +562,7 @@ impl GLES1OnGL2 {
stride
};
let buffer = &mut self.fixed_point_translation_buffers[i];
let buffer = &mut self.state.fixed_point_translation_buffers[i];
buffer.clear();
buffer.resize(((first + count) * size).try_into().unwrap(), 0.0);
@@ -568,7 +641,10 @@ impl GLES1OnGL2 {
gl21::ACTIVE_TEXTURE,
&mut active_texture as *mut _ as *mut _,
);
assert!(self.fixed_point_texture_units.contains(&active_texture));
assert!(self
.state
.fixed_point_texture_units
.contains(&active_texture));
let mut old_client_active_texture: GLenum = 0;
gl21::GetIntegerv(
gl21::CLIENT_ACTIVE_TEXTURE,
@@ -586,25 +662,8 @@ impl GLES1OnGL2 {
}
}
}
impl GLES for GLES1OnGL2 {
fn description() -> &'static str {
"OpenGL ES 1.1 via touchHLE GLES1-on-GL2 layer"
}
fn new(window: &mut Window) -> Result<Self, String> {
Ok(Self {
gl_ctx: window.create_gl_context(GLVersion::GL21Compat)?,
pointer_is_fixed_point: [false; ARRAYS.len()],
fixed_point_texture_units: HashSet::new(),
fixed_point_translation_buffers: [Vec::new(), Vec::new(), Vec::new(), Vec::new()],
})
}
fn make_current(&self, window: &Window) {
unsafe { window.make_gl_context_current(&self.gl_ctx) };
gl21::load_with(|s| window.gl_get_proc_address(s))
}
impl GLES for GLES1OnGL2<'_> {
unsafe fn driver_description(&self) -> String {
let version = CStr::from_ptr(gl21::GetString(gl21::VERSION) as *const _);
let vendor = CStr::from_ptr(gl21::GetString(gl21::VENDOR) as *const _);
@@ -617,7 +676,6 @@ impl GLES for GLES1OnGL2 {
renderer.to_string_lossy()
)
}
// Generic state manipulation
unsafe fn GetError(&mut self) -> GLenum {
gl21::GetError()
@@ -1155,22 +1213,22 @@ impl GLES for GLES1OnGL2 {
assert!(size == 4);
if type_ == gles11::FIXED {
// Translation deferred until draw call
self.pointer_is_fixed_point[0] = true;
self.state.pointer_is_fixed_point[0] = true;
gl21::ColorPointer(size, gl21::FLOAT, stride, pointer)
} else {
assert!(type_ == gl21::UNSIGNED_BYTE || type_ == gl21::FLOAT);
self.pointer_is_fixed_point[0] = false;
self.state.pointer_is_fixed_point[0] = false;
gl21::ColorPointer(size, type_, stride, pointer)
}
}
unsafe fn NormalPointer(&mut self, type_: GLenum, stride: GLsizei, pointer: *const GLvoid) {
if type_ == gles11::FIXED {
// Translation deferred until draw call
self.pointer_is_fixed_point[1] = true;
self.state.pointer_is_fixed_point[1] = true;
gl21::NormalPointer(gl21::FLOAT, stride, pointer)
} else {
assert!(type_ == gl21::BYTE || type_ == gl21::SHORT || type_ == gl21::FLOAT);
self.pointer_is_fixed_point[1] = false;
self.state.pointer_is_fixed_point[1] = false;
gl21::NormalPointer(type_, stride, pointer)
}
}
@@ -1190,15 +1248,15 @@ impl GLES for GLES1OnGL2 {
if type_ == gles11::FIXED {
// Translation deferred until draw call.
// There is one texture co-ordinates pointer per texture unit.
self.fixed_point_texture_units.insert(active_texture);
self.pointer_is_fixed_point[2] = true;
self.state.fixed_point_texture_units.insert(active_texture);
self.state.pointer_is_fixed_point[2] = true;
gl21::TexCoordPointer(size, gl21::FLOAT, stride, pointer)
} else {
// TODO: byte
assert!(type_ == gl21::SHORT || type_ == gl21::FLOAT);
self.fixed_point_texture_units.remove(&active_texture);
if self.fixed_point_texture_units.is_empty() {
self.pointer_is_fixed_point[2] = false;
self.state.fixed_point_texture_units.remove(&active_texture);
if self.state.fixed_point_texture_units.is_empty() {
self.state.pointer_is_fixed_point[2] = false;
}
gl21::TexCoordPointer(size, type_, stride, pointer)
}
@@ -1213,12 +1271,12 @@ impl GLES for GLES1OnGL2 {
assert!(size == 2 || size == 3 || size == 4);
if type_ == gles11::FIXED {
// Translation deferred until draw call
self.pointer_is_fixed_point[3] = true;
self.state.pointer_is_fixed_point[3] = true;
gl21::VertexPointer(size, gl21::FLOAT, stride, pointer)
} else {
// TODO: byte
assert!(type_ == gl21::SHORT || type_ == gl21::FLOAT);
self.pointer_is_fixed_point[3] = false;
self.state.pointer_is_fixed_point[3] = false;
gl21::VertexPointer(size, type_, stride, pointer)
}
}
@@ -1261,70 +1319,73 @@ impl GLES for GLES1OnGL2 {
.contains(&mode));
assert!(type_ == gl21::UNSIGNED_BYTE || type_ == gl21::UNSIGNED_SHORT);
let fixed_point_arrays_state_backup =
if self.pointer_is_fixed_point.iter().any(|&is_fixed| is_fixed) {
// Scan the index buffer to find the range of data that may need
// fixed-point translation.
// TODO: Would it be more efficient to turn this into a
// non-indexed draw-call instead?
let fixed_point_arrays_state_backup = if self
.state
.pointer_is_fixed_point
.iter()
.any(|&is_fixed| is_fixed)
{
// Scan the index buffer to find the range of data that may need
// fixed-point translation.
// TODO: Would it be more efficient to turn this into a
// non-indexed draw-call instead?
let mut index_buffer_binding = 0;
gl21::GetIntegerv(
gl21::ELEMENT_ARRAY_BUFFER_BINDING,
&mut index_buffer_binding,
);
let indices = if index_buffer_binding != 0 {
let mapped_buffer =
gl21::MapBuffer(gl21::ELEMENT_ARRAY_BUFFER, gl21::READ_ONLY);
assert!(!mapped_buffer.is_null());
// in this case the indices is actually an offest!
mapped_buffer.add(indices as usize)
} else {
indices
};
let mut first = usize::MAX;
let mut last = usize::MIN;
assert!(count >= 0);
match type_ {
gl21::UNSIGNED_BYTE => {
let indices_ptr: *const GLubyte = indices.cast();
for i in 0..(count as usize) {
let index = indices_ptr.add(i).read_unaligned();
first = first.min(index as usize);
last = last.max(index as usize);
}
}
gl21::UNSIGNED_SHORT => {
let indices_ptr: *const GLushort = indices.cast();
for i in 0..(count as usize) {
let index = indices_ptr.add(i).read_unaligned();
first = first.min(index as usize);
last = last.max(index as usize);
}
}
_ => unreachable!(),
}
let (first, count) = if first == usize::MAX && last == usize::MIN {
assert!(count == 0);
(0, 0)
} else {
(
first.try_into().unwrap(),
(last + 1 - first).try_into().unwrap(),
)
};
if index_buffer_binding != 0 {
gl21::UnmapBuffer(gl21::ELEMENT_ARRAY_BUFFER);
}
Some(self.translate_fixed_point_arrays(first, count))
let mut index_buffer_binding = 0;
gl21::GetIntegerv(
gl21::ELEMENT_ARRAY_BUFFER_BINDING,
&mut index_buffer_binding,
);
let indices = if index_buffer_binding != 0 {
let mapped_buffer = gl21::MapBuffer(gl21::ELEMENT_ARRAY_BUFFER, gl21::READ_ONLY);
assert!(!mapped_buffer.is_null());
// in this case the indices is actually an offest!
mapped_buffer.add(indices as usize)
} else {
None
indices
};
let mut first = usize::MAX;
let mut last = usize::MIN;
assert!(count >= 0);
match type_ {
gl21::UNSIGNED_BYTE => {
let indices_ptr: *const GLubyte = indices.cast();
for i in 0..(count as usize) {
let index = indices_ptr.add(i).read_unaligned();
first = first.min(index as usize);
last = last.max(index as usize);
}
}
gl21::UNSIGNED_SHORT => {
let indices_ptr: *const GLushort = indices.cast();
for i in 0..(count as usize) {
let index = indices_ptr.add(i).read_unaligned();
first = first.min(index as usize);
last = last.max(index as usize);
}
}
_ => unreachable!(),
}
let (first, count) = if first == usize::MAX && last == usize::MIN {
assert!(count == 0);
(0, 0)
} else {
(
first.try_into().unwrap(),
(last + 1 - first).try_into().unwrap(),
)
};
if index_buffer_binding != 0 {
gl21::UnmapBuffer(gl21::ELEMENT_ARRAY_BUFFER);
}
Some(self.translate_fixed_point_arrays(first, count))
} else {
None
};
gl21::DrawElements(mode, count, type_, indices);
if let Some(fixed_point_arrays_state_backup) = fixed_point_arrays_state_backup {

View File

@@ -9,16 +9,16 @@
//! usage is to import `GLES` and `types` from this module, but get the
//! constants from [super::gles11_raw].
use crate::window::{GLContext, Window};
use super::gles11_raw::types::*;
/// Trait representing an OpenGL ES implementation and context.
///
/// # Safety
/// It is the caller's responsibility to make the context active before using
/// any of the `unsafe` methods of this trait.
/// The GL context is not necessarily active, so GL functions can't be called
/// from this trait. It can be made active from [GLESContext::make_current].
#[allow(clippy::upper_case_acronyms)]
#[allow(clippy::too_many_arguments)] // not our fault :(
pub trait GLES {
pub trait GLESContext {
/// Get a human-friendly description of this implementation.
fn description() -> &'static str
where
@@ -33,12 +33,47 @@ pub trait GLES {
/// Make this context (and any underlying context) the active OpenGL
/// context.
fn make_current(&self, window: &crate::window::Window);
///
/// The lifetime ensures safety - the GLES object can't be destroyed while
/// the instance is active, so the OpenGL state remains valid, and the
/// window reference prevents the thread from yielding while the GLES
/// object is being used, and prevents multiple contexts from existing at
/// the same time (which can cause a UAF).
fn make_current<'gl_ctx, 'win: 'gl_ctx>(
&'gl_ctx mut self,
window: &'win mut Window,
) -> Box<dyn GLES + 'gl_ctx>;
/// Make this context (and any underlying context) the active OpenGL
/// context, without checking if it is the only context. You shouldn't use
/// this outside of [crate::window::Window], as this is function exists to
/// work around lifetime splitting issues inside of it.
///
/// SAFETY: Callers must ensure that this is the only active context,
/// that the GLES instance does not outlive the self or window
/// parameter, that make_current_fn makes the passed context current,
/// and that loader_fn properly loads the requested function.
unsafe fn make_current_unchecked_for_window<'gl_ctx>(
&'gl_ctx mut self,
make_current_fn: &mut dyn FnMut(&GLContext),
loader_fn: &mut dyn FnMut(&'static str) -> *const std::ffi::c_void,
) -> Box<dyn GLES + 'gl_ctx>;
}
/// An active GLES context that can be used.
///
/// These are effectively direct wrappers around the raw OpenGL functions,
/// but they make sure that the context is active while it is using it.
/// # Safety
/// These functions (should) act as documented by the OpenGL ES spec. Callers
/// should ensure that all uses of raw pointers are verfied to be valid and
/// of the correct size as documented in the OpenGL ES spec.
#[allow(clippy::upper_case_acronyms)]
#[allow(clippy::too_many_arguments)] // not our fault :(
pub trait GLES {
/// Get some string describing the underlying driver. For OpenGL this is
/// `GL_VENDOR`, `GL_RENDERER` and `GL_VERSION`.
unsafe fn driver_description(&self) -> String;
// Generic state manipulation
unsafe fn GetError(&mut self) -> GLenum;
unsafe fn Enable(&mut self, cap: GLenum);

View File

@@ -13,7 +13,7 @@
//! will be needed for the runtime of the app.
use crate::gles::present::present_frame;
use crate::gles::{create_gles1_ctx, GLES};
use crate::gles::{create_gles1_ctx, GLESContext, GLES};
use crate::image::Image;
use crate::matrix::Matrix;
use crate::options::Options;
@@ -132,6 +132,12 @@ pub enum GLVersion {
pub struct GLContext(sdl2::video::GLContext);
impl GLContext {
pub fn is_current(&self) -> bool {
self.0.is_current()
}
}
fn surface_from_image(image: &Image) -> Surface<'_> {
let src_pixels = image.pixels();
let (width, height) = image.dimensions();
@@ -172,10 +178,9 @@ pub struct Window {
/// [Self::rotatable_fullscreen] returns [true].
fullscreen: bool,
scale_hack: NonZeroU32,
internal_gl_ctx: Option<Box<dyn GLES>>,
internal_gl_ins: Option<Box<dyn GLESContext>>,
splash_image: Option<Image>,
device_orientation: DeviceOrientation,
app_gl_ctx_no_longer_current: bool,
controller_ctx: sdl2::GameControllerSubsystem,
controllers: Vec<sdl2::controller::GameController>,
dpad_state: DpadState,
@@ -313,10 +318,9 @@ impl Window {
viewport_y_offset: 0,
fullscreen,
scale_hack,
internal_gl_ctx: None,
internal_gl_ins: None,
splash_image: launch_image,
device_orientation,
app_gl_ctx_no_longer_current: false,
controller_ctx,
controllers: Vec::new(),
dpad_state: DpadState {
@@ -338,10 +342,12 @@ impl Window {
// (see src/frameworks/core_animation/composition.rs). OpenGL ES is used
// because SDL2 won't let us use more than one graphics API in the same
// window, and we also need OpenGL ES for the app's own rendering.
let gl_ctx = create_gles1_ctx(&mut window, options);
gl_ctx.make_current(&window);
log!("Driver info: {}", unsafe { gl_ctx.driver_description() });
window.internal_gl_ctx = Some(gl_ctx);
let mut gl_ins = create_gles1_ctx(&mut window, options);
{
let gl_ctx = gl_ins.make_current(&mut window);
log!("Driver info: {}", unsafe { gl_ctx.driver_description() });
}
window.internal_gl_ins = Some(gl_ins);
if window.splash_image.is_some() {
window.display_splash();
@@ -1108,28 +1114,23 @@ impl Window {
self.window.gl_make_current(&gl_ctx.0).unwrap();
}
/// Retrieve and reset the flag that indicates if the current OpenGL context
/// was changed to one outside of the control of the guest app.
///
/// This should be checked before making OpenGL calls on behalf of the guest
/// app, so its context can be restored.
pub fn is_app_gl_ctx_no_longer_current(&mut self) -> bool {
let value = self.app_gl_ctx_no_longer_current;
self.app_gl_ctx_no_longer_current = false;
value
}
/// Make the internal OpenGL ES context (for splash screen and UI rendering)
/// current.
pub fn make_internal_gl_ctx_current(&mut self) {
self.app_gl_ctx_no_longer_current = true;
self.internal_gl_ctx.as_ref().unwrap().make_current(self);
}
/// Get the internal OpenGL ES context (for splash screen and UI rendering).
/// This does not ensure the context is current.
pub fn get_internal_gl_ctx(&mut self) -> &mut dyn GLES {
self.internal_gl_ctx.as_deref_mut().unwrap()
#[must_use]
pub fn make_internal_gl_ctx_current<'win>(&'win mut self) -> Box<dyn GLES + 'win> {
// The invariant is held up here - since the instance we return is
// bound to the lifetime of window, it can't outlive the internal GL
// context and can't outlive the window.
let gl_ins = unsafe {
self.internal_gl_ins
.as_mut()
.unwrap()
.make_current_unchecked_for_window(
&mut |gl_ctx| self.window.gl_make_current(&gl_ctx.0).unwrap(),
&mut |s| self.video_ctx.gl_get_proc_address(s) as *const _,
)
};
gl_ins
}
fn display_splash(&mut self) {
@@ -1141,14 +1142,20 @@ impl Window {
let (vx, vy, vw, vh) = self.viewport();
let viewport = (vx, vy + self.viewport_y_offset(), vw, vh);
self.make_internal_gl_ctx_current();
let image = self.splash_image.as_ref().unwrap();
let gl_ctx = self.internal_gl_ctx.as_deref_mut().unwrap();
use crate::gles::gles11_raw as gles11; // constants only
unsafe {
let mut gl_ctx = self
.internal_gl_ins
.as_mut()
.unwrap()
.make_current_unchecked_for_window(
&mut |gl_ctx| self.window.gl_make_current(&gl_ctx.0).unwrap(),
&mut |s| self.video_ctx.gl_get_proc_address(s) as *const _,
);
use crate::gles::gles11_raw as gles11; // constants only
let mut texture = 0;
gl_ctx.GenTextures(1, &mut texture);
gl_ctx.BindTexture(gles11::TEXTURE_2D, texture);
@@ -1176,7 +1183,10 @@ impl Window {
);
present_frame(
gl_ctx, viewport, matrix, /* virtual_cursor_visible_at: */ None,
gl_ctx.as_mut(),
viewport,
matrix,
/* virtual_cursor_visible_at: */ None,
);
gl_ctx.DeleteTextures(1, &texture);