use core::ffi::c_void;
use crate::{
gl::{
self,
types::{GLint, GLuint},
},
wgl,
};
use anyhow::{Context, bail};
use scopeguard::{ScopeGuard, defer};
use tracing::trace;
use windows::{
Win32::Graphics::{
Direct3D11::{D3D11_TEXTURE2D_DESC, ID3D11Device, ID3D11Texture2D},
Dxgi::{
Common::{
DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, DXGI_FORMAT_R16G16B16A16_FLOAT,
DXGI_FORMAT_R16G16B16A16_UNORM,
},
IDXGIResource,
},
},
core::Interface,
};
static VERTEX_SHADER: &str = include_str!("opengl/shaders/texture.vert");
static FRAGMENT_SHADER: &str = include_str!("opengl/shaders/texture.frag");
pub struct OpenglRenderer {
interop: Option<GlInteropTexture>,
program: GLuint,
rect_loc: GLint,
tex_loc: GLint,
}
impl OpenglRenderer {
#[tracing::instrument]
pub fn new() -> anyhow::Result<Self> {
unsafe {
let vert_shader = gl::CreateShader(gl::VERTEX_SHADER);
gl::ShaderSource(
vert_shader,
1,
(&raw const VERTEX_SHADER).cast(),
&(VERTEX_SHADER.len() as i32),
);
gl::CompileShader(vert_shader);
let frag_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
gl::ShaderSource(
frag_shader,
1,
(&raw const FRAGMENT_SHADER).cast(),
&(FRAGMENT_SHADER.len() as i32),
);
gl::CompileShader(frag_shader);
let program = gl::CreateProgram();
gl::AttachShader(program, vert_shader);
gl::AttachShader(program, frag_shader);
gl::LinkProgram(program);
let rect_loc = gl::GetUniformLocation(program, b"rect\0" as *const _ as _);
let tex_loc = gl::GetUniformLocation(program, b"tex\0" as *const _ as _);
gl::DeleteShader(vert_shader);
gl::DeleteShader(frag_shader);
Ok(Self {
interop: None,
program,
rect_loc,
tex_loc,
})
}
}
pub fn update_texture(&mut self, texture: Option<&ID3D11Texture2D>) -> anyhow::Result<()> {
self.interop.take();
let Some(texture) = texture else {
return Ok(());
};
let mut desc = D3D11_TEXTURE2D_DESC::default();
unsafe { texture.GetDesc(&mut desc) };
let size = (desc.Width, desc.Height);
if size.0 == 0 || size.1 == 0 {
return Ok(());
}
self.interop = Some(GlInteropTexture::new(
texture,
desc.Format,
(size.0 as _, size.1 as _),
)?);
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn draw(
&mut self,
position: (i32, i32),
size: (u32, u32),
screen: (u32, u32),
) -> anyhow::Result<()> {
if screen.0 == 0 || screen.1 == 0 {
return Ok(());
}
let Some(ref texture) = self.interop else {
return Ok(());
};
let rect: [f32; 4] = [
(position.0 as f32 / screen.0 as f32) * 2.0 - 1.0,
-(position.1 as f32 / screen.1 as f32) * 2.0 + 1.0,
(size.0 as f32 / screen.0 as f32) * 2.0,
-(size.1 as f32 / screen.1 as f32) * 2.0,
];
unsafe {
gl::Viewport(0, 0, screen.0 as _, screen.1 as _);
gl::Enable(gl::BLEND);
gl::BlendEquation(gl::FUNC_ADD);
gl::BlendFuncSeparate(
gl::SRC_ALPHA,
gl::ONE_MINUS_SRC_ALPHA,
gl::ONE,
gl::ONE_MINUS_SRC_ALPHA,
);
gl::Disable(gl::CULL_FACE);
gl::Disable(gl::DEPTH_TEST);
gl::Disable(gl::STENCIL_TEST);
gl::UseProgram(self.program);
gl::Uniform4f(self.rect_loc, rect[0], rect[1], rect[2], rect[3]);
gl::Uniform1i(self.tex_loc, 0);
gl::ActiveTexture(gl::TEXTURE0);
texture.bind(gl::TEXTURE_2D, || {
gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4);
});
}
Ok(())
}
}
impl Drop for OpenglRenderer {
#[tracing::instrument(skip(self))]
fn drop(&mut self) {
self.interop.take();
unsafe {
gl::DeleteProgram(self.program);
}
trace!("OpenGL resources freed");
}
}
unsafe impl Send for OpenglRenderer {}
unsafe impl Sync for OpenglRenderer {}
enum GlInteropTexture {
MemoryObject(MemoryObjectTexture),
Wgl(NvInteropTexture),
}
impl GlInteropTexture {
pub fn new(
texture: &ID3D11Texture2D,
format: DXGI_FORMAT,
size: (u32, u32),
) -> anyhow::Result<Self> {
if gl::ImportMemoryWin32HandleEXT::is_loaded()
&& let Ok(memory_object) = MemoryObjectTexture::open(texture, format, size)
{
Ok(Self::MemoryObject(memory_object))
} else if wgl::DXOpenDeviceNV::is_loaded() {
let device = unsafe { texture.GetDevice()? };
Ok(Self::Wgl(NvInteropTexture::open(&device, texture)?))
} else {
bail!("Opengl interop is not supported");
}
}
#[inline]
pub fn bind(&self, target: gl::types::GLenum, f: impl FnOnce()) {
match *self {
Self::MemoryObject(ref texture) => texture.bind(target, f),
Self::Wgl(ref texture) => texture.bind(target, f),
}
}
}
struct MemoryObjectTexture {
memory_object: GLuint,
id: GLuint,
}
impl MemoryObjectTexture {
fn open(
texture: &ID3D11Texture2D,
format: DXGI_FORMAT,
size: (u32, u32),
) -> anyhow::Result<Self> {
unsafe {
let handle = texture.cast::<IDXGIResource>()?.GetSharedHandle()?.0.cast();
let memory_object = scopeguard::guard(
{
let mut id = 0;
gl::CreateMemoryObjectsEXT(1, &mut id);
id
},
|value| {
gl::DeleteMemoryObjectsEXT(1, &value);
},
);
_ = gl::GetError();
gl::ImportMemoryWin32HandleEXT(
*memory_object,
0,
gl::HANDLE_TYPE_D3D11_IMAGE_KMT_EXT,
handle,
);
if gl::GetError() != gl::NO_ERROR {
bail!("ImportMemoryWin32HandleEXT failed");
}
let texture = scopeguard::guard(
{
let mut id = 0;
gl::GenTextures(1, &mut id);
id
},
|value| {
gl::DeleteTextures(1, &value);
},
);
gl::ActiveTexture(gl::TEXTURE0);
gl::BindTexture(gl::TEXTURE_2D, *texture);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as _);
gl::TexStorageMem2DEXT(
gl::TEXTURE_2D,
1,
map_dxgi_to_gl(format).context("Unsupported DXGI format")?,
size.0 as _,
size.1 as _,
*memory_object,
0,
);
if gl::GetError() != gl::NO_ERROR {
bail!("TexStorageMem2DEXT failed");
}
Ok(Self {
memory_object: ScopeGuard::into_inner(memory_object),
id: ScopeGuard::into_inner(texture),
})
}
}
#[inline]
pub fn bind(&self, target: gl::types::GLenum, f: impl FnOnce()) {
unsafe { gl::BindTexture(target, self.id) };
f();
}
}
impl Drop for MemoryObjectTexture {
fn drop(&mut self) {
unsafe {
gl::DeleteTextures(1, &self.id);
gl::DeleteMemoryObjectsEXT(1, &self.memory_object);
}
}
}
unsafe impl Send for MemoryObjectTexture {}
struct NvInteropTexture {
device_handle: *const c_void,
dx11_tex_handle: *const c_void,
gl_texture: GLuint,
}
impl NvInteropTexture {
fn open(device: &ID3D11Device, texture: &ID3D11Texture2D) -> anyhow::Result<Self> {
unsafe {
let dx_device_handle = wgl::DXOpenDeviceNV(device.as_raw());
if dx_device_handle.is_null() {
bail!("DXOpenDeviceNV failed");
}
let mut gl_texture = 0;
gl::GenTextures(1, &mut gl_texture);
let mut last_id = 0;
gl::GetIntegerv(gl::TEXTURE_BINDING_2D, &mut last_id);
gl::BindTexture(gl::TEXTURE_2D, gl_texture);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as _);
gl::BindTexture(gl::TEXTURE_2D, last_id as _);
let dx11_tex_handle = wgl::DXRegisterObjectNV(
dx_device_handle,
texture.as_raw(),
gl_texture,
gl::TEXTURE_2D,
wgl::ACCESS_READ_ONLY_NV,
);
if dx11_tex_handle.is_null() {
wgl::DXCloseDeviceNV(dx_device_handle as _);
gl::DeleteTextures(1, &gl_texture);
bail!("DXRegisterObjectNV failed");
}
Ok(NvInteropTexture {
device_handle: dx_device_handle,
dx11_tex_handle,
gl_texture,
})
}
}
#[inline]
fn bind(&self, target: gl::types::GLenum, f: impl FnOnce()) {
unsafe {
gl::BindTexture(target, self.gl_texture);
wgl::DXLockObjectsNV(
self.device_handle,
1,
&self.dx11_tex_handle as *const _ as _,
);
defer!({
wgl::DXUnlockObjectsNV(
self.device_handle,
1,
&self.dx11_tex_handle as *const _ as _,
);
});
f();
}
}
}
impl Drop for NvInteropTexture {
fn drop(&mut self) {
unsafe {
wgl::DXUnregisterObjectNV(self.device_handle, self.dx11_tex_handle);
gl::DeleteTextures(1, &self.gl_texture);
wgl::DXCloseDeviceNV(self.device_handle as _);
}
}
}
unsafe impl Send for NvInteropTexture {}
fn map_dxgi_to_gl(format: DXGI_FORMAT) -> Option<GLuint> {
match format {
DXGI_FORMAT_R8G8B8A8_UNORM => Some(gl::RGBA8),
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => Some(gl::SRGB8_ALPHA8),
DXGI_FORMAT_B8G8R8A8_UNORM => Some(gl::BGRA),
DXGI_FORMAT_R16G16B16A16_UNORM => Some(gl::RGBA16),
DXGI_FORMAT_R16G16B16A16_FLOAT => Some(gl::RGBA16F),
_ => None,
}
}