mangayomi-mirror/packages/media_kit_video/windows/angle_surface_manager.cc
kodjomoustapha 7cd4ab860c fix #206
2024-06-29 10:48:47 +01:00

309 lines
11 KiB
C++

// This file is a part of media_kit
// (https://github.com/media-kit/media-kit).
//
// Copyright © 2021 & onwards, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
// All rights reserved.
// Use of this source code is governed by MIT license that can be found in the
// LICENSE file.
#include "angle_surface_manager.h"
#include <iostream>
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#define FAIL(message) \
std::cout << "media_kit: ANGLESurfaceManager: Failure: " << message \
<< std::endl; \
return false
#define CHECK_HRESULT(message) \
if (FAILED(hr)) { \
FAIL(message); \
}
int ANGLESurfaceManager::instance_count_ = 0;
ANGLESurfaceManager::ANGLESurfaceManager(int32_t width, int32_t height)
: width_(width), height_(height) {
mutex_ = ::CreateMutex(NULL, FALSE, NULL);
Create();
instance_count_++;
}
ANGLESurfaceManager::~ANGLESurfaceManager() {
CleanUp(true);
::ReleaseMutex(mutex_);
::CloseHandle(mutex_);
instance_count_--;
}
void ANGLESurfaceManager::HandleResize(int32_t width, int32_t height) {
if (width == width_ && height == height_) {
return;
}
width_ = width;
height_ = height;
Create();
}
void ANGLESurfaceManager::Draw(std::function<void()> callback) {
::WaitForSingleObject(mutex_, INFINITE);
MakeCurrent(true);
callback();
SwapBuffers();
MakeCurrent(false);
::ReleaseMutex(mutex_);
}
void ANGLESurfaceManager::Read() {
::WaitForSingleObject(mutex_, INFINITE);
if (d3d_11_device_context_ != nullptr) {
d3d_11_device_context_->CopyResource(d3d_11_texture_2D_.Get(),
internal_d3d_11_texture_2D_.Get());
d3d_11_device_context_->Flush();
}
::ReleaseMutex(mutex_);
}
void ANGLESurfaceManager::MakeCurrent(bool value) {
if (value) {
eglMakeCurrent(display_, surface_, surface_, context_);
} else {
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
}
void ANGLESurfaceManager::SwapBuffers() {
glFinish();
}
void ANGLESurfaceManager::Create() {
CleanUp(false);
if (!CreateD3DTexture()) {
throw std::runtime_error("Unable to create Windows Direct3D device.");
return;
}
if (!CreateEGLDisplay()) {
throw std::runtime_error("Unable to create ANGLE EGL display.");
return;
}
if (!CreateAndBindEGLSurface()) {
throw std::runtime_error("Unable to create ANGLE EGL surface.");
return;
}
if (internal_handle_ == nullptr || handle_ == nullptr) {
throw std::runtime_error("Unable to retrieve Direct3D shared HANDLE.");
return;
}
}
void ANGLESurfaceManager::CleanUp(bool release_context) {
if (release_context) {
if (display_ != EGL_NO_DISPLAY && surface_ != EGL_NO_SURFACE) {
eglReleaseTexImage(display_, surface_, EGL_BACK_BUFFER);
}
if (display_ != EGL_NO_DISPLAY && context_ != EGL_NO_CONTEXT) {
eglDestroyContext(display_, context_);
context_ = EGL_NO_CONTEXT;
}
if (surface_ != EGL_NO_SURFACE) {
eglDestroySurface(display_, surface_);
surface_ = EGL_NO_SURFACE;
}
if (instance_count_ == 1) {
eglTerminate(display_);
}
display_ = EGL_NO_DISPLAY;
// Release D3D device & context if the instance is being destroyed.
if (d3d_11_device_context_) {
d3d_11_device_context_->Release();
d3d_11_device_context_ = nullptr;
}
if (d3d_11_device_) {
d3d_11_device_->Release();
d3d_11_device_ = nullptr;
}
} else {
// Clear context & destroy existing |surface_|.
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_);
if (display_ != EGL_NO_DISPLAY && surface_ != EGL_NO_SURFACE) {
eglDestroySurface(display_, surface_);
}
surface_ = EGL_NO_SURFACE;
}
// Release D3D 11 texture(s).
if (internal_d3d_11_texture_2D_) {
internal_d3d_11_texture_2D_->Release();
internal_d3d_11_texture_2D_ = nullptr;
}
if (d3d_11_texture_2D_) {
d3d_11_texture_2D_->Release();
d3d_11_texture_2D_ = nullptr;
}
}
bool ANGLESurfaceManager::CreateD3DTexture() {
if (adapter_ == nullptr) {
auto feature_levels = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
};
// NOTE: Not enabling DirectX 12.
// |D3D11CreateDevice| crashes directly on Windows 7.
// D3D_FEATURE_LEVEL_12_2, D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0,
// D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
// D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3,
IDXGIFactory* dxgi = nullptr;
::CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&dxgi);
// Manually selecting adapter. As far as my experience goes, this is the
// safest approach. Passing NULL (so-called default) seems to cause issues
// on Windows 7 or maybe some older graphics drivers.
// First adapter is the default.
// |D3D_DRIVER_TYPE_UNKNOWN| must be passed with manual adapter selection.
dxgi->EnumAdapters(0, &adapter_);
dxgi->Release();
if (!adapter_) {
FAIL("No IDXGIAdapter found.");
} else {
// Just for debugging.
DXGI_ADAPTER_DESC adapter_desc_;
adapter_->GetDesc(&adapter_desc_);
std::wcout << adapter_desc_.Description << std::endl;
}
auto hr = ::D3D11CreateDevice(
adapter_, D3D_DRIVER_TYPE_UNKNOWN, 0, 0, feature_levels.begin(),
static_cast<UINT>(feature_levels.size()), D3D11_SDK_VERSION,
&d3d_11_device_, 0, &d3d_11_device_context_);
CHECK_HRESULT("D3D11CreateDevice");
}
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device = nullptr;
auto dxgi_device_success = d3d_11_device_->QueryInterface(
__uuidof(IDXGIDevice), (void**)&dxgi_device);
if (SUCCEEDED(dxgi_device_success) && dxgi_device != nullptr) {
dxgi_device->SetGPUThreadPriority(5); // Must be in interval [-7, 7].
}
auto level = d3d_11_device_->GetFeatureLevel();
std::cout << "media_kit: ANGLESurfaceManager: Direct3D Feature Level: "
<< (((unsigned)level) >> 12) << "_"
<< ((((unsigned)level) >> 8) & 0xf) << std::endl;
auto d3d11_texture2D_desc = D3D11_TEXTURE2D_DESC{0};
d3d11_texture2D_desc.Width = width_;
d3d11_texture2D_desc.Height = height_;
d3d11_texture2D_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
d3d11_texture2D_desc.MipLevels = 1;
d3d11_texture2D_desc.ArraySize = 1;
d3d11_texture2D_desc.SampleDesc.Count = 1;
d3d11_texture2D_desc.SampleDesc.Quality = 0;
d3d11_texture2D_desc.Usage = D3D11_USAGE_DEFAULT;
d3d11_texture2D_desc.BindFlags =
D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
d3d11_texture2D_desc.CPUAccessFlags = 0;
d3d11_texture2D_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
// The general idea is to create two textures, one that is used to |Draw|
// using ANGLE & another one that is used for |Read| the rendered content
// using |handle|.
// The internal texture is copied to the public texture once a frame is
// requested using |ID3D11DeviceContext::CopyResource|. This prevents any kind
// of synchronization issues.
// Internal.
auto hr = d3d_11_device_->CreateTexture2D(&d3d11_texture2D_desc, nullptr,
&internal_d3d_11_texture_2D_);
CHECK_HRESULT("ID3D11Device::CreateTexture2D");
auto resource = Microsoft::WRL::ComPtr<IDXGIResource>{};
hr = internal_d3d_11_texture_2D_.As(&resource);
CHECK_HRESULT("ID3D11Texture2D::As");
// Retrieve the shared |HANDLE| for interop.
hr = resource->GetSharedHandle(&internal_handle_);
CHECK_HRESULT("IDXGIResource::GetSharedHandle");
internal_d3d_11_texture_2D_->AddRef();
// External.
hr = d3d_11_device_->CreateTexture2D(&d3d11_texture2D_desc, nullptr,
&d3d_11_texture_2D_);
CHECK_HRESULT("ID3D11Device::CreateTexture2D");
hr = d3d_11_texture_2D_.As(&resource);
CHECK_HRESULT("ID3D11Texture2D::As");
// Retrieve the shared |HANDLE| for interop.
hr = resource->GetSharedHandle(&handle_);
CHECK_HRESULT("IDXGIResource::GetSharedHandle");
d3d_11_texture_2D_->AddRef();
return true;
}
bool ANGLESurfaceManager::CreateEGLDisplay() {
if (display_ == EGL_NO_DISPLAY) {
auto eglGetPlatformDisplayEXT =
reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplayEXT"));
if (eglGetPlatformDisplayEXT) {
display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
EGL_DEFAULT_DISPLAY,
kD3D11DisplayAttributes);
if (eglInitialize(display_, 0, 0) == EGL_FALSE) {
display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
EGL_DEFAULT_DISPLAY,
kD3D11_9_3DisplayAttributes);
if (eglInitialize(display_, 0, 0) == EGL_FALSE) {
display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
EGL_DEFAULT_DISPLAY,
kD3D9DisplayAttributes);
if (eglInitialize(display_, 0, 0) == EGL_FALSE) {
display_ = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
EGL_DEFAULT_DISPLAY,
kWrapDisplayAttributes);
if (eglInitialize(display_, 0, 0) == EGL_FALSE) {
FAIL("eglGetPlatformDisplayEXT");
}
}
}
}
} else {
FAIL("eglGetProcAddress");
}
}
return true;
}
bool ANGLESurfaceManager::CreateAndBindEGLSurface() {
// Do not create |context_| again, likely due to |Resize|.
if (context_ == EGL_NO_CONTEXT) {
// First time from the constructor itself.
auto count = 0;
auto result = eglChooseConfig(display_, kEGLConfigurationAttributes,
&config_, 1, &count);
if (result == EGL_FALSE || count == 0) {
FAIL("eglChooseConfig");
}
context_ = eglCreateContext(display_, config_, EGL_NO_CONTEXT,
kEGLContextAttributes);
if (context_ == EGL_NO_CONTEXT) {
FAIL("eglCreateContext");
}
}
EGLint buffer_attributes[] = {
EGL_WIDTH, width_, EGL_HEIGHT, height_,
EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
EGL_NONE,
};
surface_ = eglCreatePbufferFromClientBuffer(
display_, EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, internal_handle_,
config_, buffer_attributes);
if (surface_ == EGL_NO_SURFACE) {
FAIL("eglCreatePbufferFromClientBuffer");
}
GLuint t;
glGenTextures(1, &t);
glBindTexture(GL_TEXTURE_2D, t);
eglBindTexImage(display_, surface_, EGL_BACK_BUFFER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
return true;
}