mirror of
https://github.com/touchHLE/touchHLE.git
synced 2026-01-31 01:25:24 +01:00
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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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); } }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
30
src/gles.rs
30
src/gles.rs
@@ -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)
|
||||
|
||||
@@ -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 _);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user