From 861b98ed3ba927fb3917e94e09ffec0b4c6699b3 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 30 Jun 2020 02:47:27 +1000 Subject: [PATCH] libretro: Additional work - Reliable resolution switching. - Hook up logging. - Memory cards and controller type settings. - Save state support. - Direct3D support. --- duckstation.sln | 4 + src/CMakeLists.txt | 2 +- src/duckstation-libretro/CMakeLists.txt | 10 +- .../d3d11_host_display.cpp | 283 ------------- src/duckstation-libretro/d3d11_host_display.h | 62 --- .../duckstation-libretro.vcxproj | 14 +- .../duckstation-libretro.vcxproj.filters | 8 +- .../libretro_d3d11_host_display.cpp | 116 +++++ .../libretro_d3d11_host_display.h | 27 ++ .../libretro_host_display.cpp | 80 +++- .../libretro_host_display.h | 21 +- .../libretro_host_interface.cpp | 395 +++++++++++++++--- .../libretro_host_interface.h | 20 +- .../libretro_opengl_host_display.cpp | 143 +++++++ .../libretro_opengl_host_display.h | 31 ++ src/duckstation-libretro/main.cpp | 17 +- .../opengl_host_display.cpp | 385 ----------------- .../opengl_host_display.h | 53 --- 18 files changed, 774 insertions(+), 897 deletions(-) delete mode 100644 src/duckstation-libretro/d3d11_host_display.cpp delete mode 100644 src/duckstation-libretro/d3d11_host_display.h create mode 100644 src/duckstation-libretro/libretro_d3d11_host_display.cpp create mode 100644 src/duckstation-libretro/libretro_d3d11_host_display.h create mode 100644 src/duckstation-libretro/libretro_opengl_host_display.cpp create mode 100644 src/duckstation-libretro/libretro_opengl_host_display.h delete mode 100644 src/duckstation-libretro/opengl_host_display.cpp delete mode 100644 src/duckstation-libretro/opengl_host_display.h diff --git a/duckstation.sln b/duckstation.sln index 2380b0563..8179f7aee 100644 --- a/duckstation.sln +++ b/duckstation.sln @@ -438,15 +438,19 @@ Global {4266505B-DBAF-484B-AB31-B53B9C8235B3}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32 {4266505B-DBAF-484B-AB31-B53B9C8235B3}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.Debug|x64.ActiveCfg = Debug|x64 + {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.Debug|x64.Build.0 = Debug|x64 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.Debug|x86.ActiveCfg = Debug|Win32 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.Debug|x86.Build.0 = Debug|Win32 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.DebugFast|x64.ActiveCfg = DebugFast|x64 + {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.DebugFast|x64.Build.0 = DebugFast|x64 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.DebugFast|x86.ActiveCfg = DebugFast|Win32 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.DebugFast|x86.Build.0 = DebugFast|Win32 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.Release|x64.ActiveCfg = Release|x64 + {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.Release|x64.Build.0 = Release|x64 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.Release|x86.ActiveCfg = Release|Win32 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.Release|x86.Build.0 = Release|Win32 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64 + {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32 {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32 {7F909E29-4808-4BD9-A60C-56C51A3AAEC2}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ddbc7c58..824af1178 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,7 @@ add_subdirectory(common-tests) add_subdirectory(core) add_subdirectory(scmversion) -if(ANDROID OR BUILD_SDL_FRONTEND OR BUILD_QT_FRONTEND) +if(ANDROID OR BUILD_SDL_FRONTEND OR BUILD_QT_FRONTEND OR BUILD_LIBRETRO_CORE) add_subdirectory(frontend-common) endif() diff --git a/src/duckstation-libretro/CMakeLists.txt b/src/duckstation-libretro/CMakeLists.txt index 00d428086..e95c0be66 100644 --- a/src/duckstation-libretro/CMakeLists.txt +++ b/src/duckstation-libretro/CMakeLists.txt @@ -5,19 +5,19 @@ add_library(duckstation-libretro SHARED libretro_host_display.h libretro_host_interface.cpp libretro_host_interface.h + libretro_opengl_host_display.cpp + libretro_opengl_host_display.h libretro_settings_interface.cpp libretro_settings_interface.h main.cpp - opengl_host_display.cpp - opengl_host_display.h ) if(WIN32) target_sources(duckstation-libretro PRIVATE - d3d11_host_display.cpp - d3d11_host_display.h + libretro_d3d11_host_display.cpp + libretro_d3d11_host_display.h ) endif() -target_link_libraries(duckstation-libretro PRIVATE core common imgui glad scmversion libretro-common) +target_link_libraries(duckstation-libretro PRIVATE core common imgui glad scmversion frontend-common libretro-common) diff --git a/src/duckstation-libretro/d3d11_host_display.cpp b/src/duckstation-libretro/d3d11_host_display.cpp deleted file mode 100644 index de715d20c..000000000 --- a/src/duckstation-libretro/d3d11_host_display.cpp +++ /dev/null @@ -1,283 +0,0 @@ -#include "d3d11_host_display.h" -#include "common/assert.h" -#include "common/d3d11/shader_compiler.h" -#include "common/log.h" -#include "frontend-common/display_ps.hlsl.h" -#include "frontend-common/display_vs.hlsl.h" -#include "libretro_host_interface.h" -#include -Log_SetChannel(D3D11HostDisplay); - -#define HAVE_D3D11 -#include "libretro_d3d.h" - -class D3D11HostDisplayTexture : public HostDisplayTexture -{ -public: - template - using ComPtr = Microsoft::WRL::ComPtr; - - D3D11HostDisplayTexture(ComPtr texture, ComPtr srv, u32 width, u32 height, - bool dynamic) - : m_texture(std::move(texture)), m_srv(std::move(srv)), m_width(width), m_height(height), m_dynamic(dynamic) - { - } - ~D3D11HostDisplayTexture() override = default; - - void* GetHandle() const override { return m_srv.Get(); } - u32 GetWidth() const override { return m_width; } - u32 GetHeight() const override { return m_height; } - - ID3D11Texture2D* GetD3DTexture() const { return m_texture.Get(); } - ID3D11ShaderResourceView* GetD3DSRV() const { return m_srv.Get(); } - bool IsDynamic() const { return m_dynamic; } - - static std::unique_ptr Create(ID3D11Device* device, u32 width, u32 height, const void* data, - u32 data_stride, bool dynamic) - { - const CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_R8G8B8A8_UNORM, width, height, 1, 1, D3D11_BIND_SHADER_RESOURCE, - dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT, - dynamic ? D3D11_CPU_ACCESS_WRITE : 0, 1, 0, 0); - const D3D11_SUBRESOURCE_DATA srd{data, data_stride, data_stride * height}; - ComPtr texture; - HRESULT hr = device->CreateTexture2D(&desc, data ? &srd : nullptr, texture.GetAddressOf()); - if (FAILED(hr)) - return {}; - - const CD3D11_SHADER_RESOURCE_VIEW_DESC srv_desc(D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 1, 0, - 1); - ComPtr srv; - hr = device->CreateShaderResourceView(texture.Get(), &srv_desc, srv.GetAddressOf()); - if (FAILED(hr)) - return {}; - - return std::make_unique(std::move(texture), std::move(srv), width, height, dynamic); - } - -private: - ComPtr m_texture; - ComPtr m_srv; - u32 m_width; - u32 m_height; - bool m_dynamic; -}; - -D3D11HostDisplay::D3D11HostDisplay(ComPtr device, ComPtr context) - : m_device(std::move(device)), m_context(std::move(context)) -{ -} - -D3D11HostDisplay::~D3D11HostDisplay() = default; - -HostDisplay::RenderAPI D3D11HostDisplay::GetRenderAPI() const -{ - return HostDisplay::RenderAPI::D3D11; -} - -void* D3D11HostDisplay::GetRenderDevice() const -{ - return m_device.Get(); -} - -void* D3D11HostDisplay::GetRenderContext() const -{ - return m_context.Get(); -} - -std::unique_ptr D3D11HostDisplay::CreateTexture(u32 width, u32 height, const void* data, - u32 data_stride, bool dynamic) -{ - return D3D11HostDisplayTexture::Create(m_device.Get(), width, height, data, data_stride, dynamic); -} - -void D3D11HostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, - u32 data_stride) -{ - D3D11HostDisplayTexture* d3d11_texture = static_cast(texture); - if (!d3d11_texture->IsDynamic()) - { - const CD3D11_BOX dst_box(x, y, 0, x + width, y + height, 1); - m_context->UpdateSubresource(d3d11_texture->GetD3DTexture(), 0, &dst_box, data, data_stride, data_stride * height); - } - else - { - D3D11_MAPPED_SUBRESOURCE sr; - HRESULT hr = m_context->Map(d3d11_texture->GetD3DTexture(), 0, D3D11_MAP_WRITE_DISCARD, 0, &sr); - if (FAILED(hr)) - Panic("Failed to map dynamic host display texture"); - - char* dst_ptr = static_cast(sr.pData) + (y * sr.RowPitch) + (x * sizeof(u32)); - const char* src_ptr = static_cast(data); - if (sr.RowPitch == data_stride) - { - std::memcpy(dst_ptr, src_ptr, data_stride * height); - } - else - { - for (u32 row = 0; row < height; row++) - { - std::memcpy(dst_ptr, src_ptr, width * sizeof(u32)); - src_ptr += data_stride; - dst_ptr += sr.RowPitch; - } - } - - m_context->Unmap(d3d11_texture->GetD3DTexture(), 0); - } -} - -bool D3D11HostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) -{ - ID3D11ShaderResourceView* srv = - const_cast(static_cast(texture_handle)); - ID3D11Resource* srv_resource; - D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc; - srv->GetResource(&srv_resource); - srv->GetDesc(&srv_desc); - - if (!m_readback_staging_texture.EnsureSize(m_context.Get(), width, height, srv_desc.Format, false)) - return false; - - m_readback_staging_texture.CopyFromTexture(m_context.Get(), srv_resource, 0, x, y, 0, 0, width, height); - return m_readback_staging_texture.ReadPixels(m_context.Get(), 0, 0, width, height, out_data_stride / sizeof(u32), - static_cast(out_data)); -} - -void D3D11HostDisplay::SetVSync(bool enabled) {} - -bool D3D11HostDisplay::CreateD3DResources() -{ - HRESULT hr; - - m_display_vertex_shader = - D3D11::ShaderCompiler::CreateVertexShader(m_device.Get(), s_display_vs_bytecode, sizeof(s_display_vs_bytecode)); - m_display_pixel_shader = - D3D11::ShaderCompiler::CreatePixelShader(m_device.Get(), s_display_ps_bytecode, sizeof(s_display_ps_bytecode)); - if (!m_display_vertex_shader || !m_display_pixel_shader) - return false; - - if (!m_display_uniform_buffer.Create(m_device.Get(), D3D11_BIND_CONSTANT_BUFFER, DISPLAY_UNIFORM_BUFFER_SIZE)) - return false; - - CD3D11_RASTERIZER_DESC rasterizer_desc = CD3D11_RASTERIZER_DESC(CD3D11_DEFAULT()); - rasterizer_desc.CullMode = D3D11_CULL_NONE; - hr = m_device->CreateRasterizerState(&rasterizer_desc, m_display_rasterizer_state.GetAddressOf()); - if (FAILED(hr)) - return false; - - CD3D11_DEPTH_STENCIL_DESC depth_stencil_desc = CD3D11_DEPTH_STENCIL_DESC(CD3D11_DEFAULT()); - depth_stencil_desc.DepthEnable = FALSE; - depth_stencil_desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; - hr = m_device->CreateDepthStencilState(&depth_stencil_desc, m_display_depth_stencil_state.GetAddressOf()); - if (FAILED(hr)) - return false; - - CD3D11_BLEND_DESC blend_desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); - hr = m_device->CreateBlendState(&blend_desc, m_display_blend_state.GetAddressOf()); - if (FAILED(hr)) - return false; - - CD3D11_SAMPLER_DESC sampler_desc = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()); - sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; - hr = m_device->CreateSamplerState(&sampler_desc, m_point_sampler.GetAddressOf()); - if (FAILED(hr)) - return false; - - sampler_desc.Filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT; - hr = m_device->CreateSamplerState(&sampler_desc, m_linear_sampler.GetAddressOf()); - if (FAILED(hr)) - return false; - - return true; -} - -bool D3D11HostDisplay::RequestHardwareRendererContext(retro_hw_render_callback* cb) -{ - cb->cache_context = true; - cb->bottom_left_origin = false; - cb->context_type = RETRO_HW_CONTEXT_DIRECT3D; - cb->version_major = 11; - cb->version_minor = 0; - - return g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER, cb); -} - -std::unique_ptr D3D11HostDisplay::Create(bool debug_device) -{ - retro_hw_render_interface_d3d11 ri = {}; - ri.interface_type = RETRO_HW_RENDER_INTERFACE_D3D11; - ri.interface_version = RETRO_HW_RENDER_INTERFACE_D3D11_VERSION; - - if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, &ri)) - { - Log_ErrorPrint("Failed to get HW render interface"); - return nullptr; - } - - ComPtr device(ri.device); - ComPtr context(ri.context); - - std::unique_ptr display = std::make_unique(std::move(device), std::move(context)); - if (!display->CreateD3DResources()) - return nullptr; - - return display; -} - -void D3D11HostDisplay::Render() -{ -#if 0 - static constexpr std::array clear_color = {}; - m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), clear_color.data()); - m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr); - - RenderDisplay(); - - if (!m_vsync && m_allow_tearing_supported) - m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); - else - m_swap_chain->Present(BoolToUInt32(m_vsync), 0); - - ImGui::NewFrame(); - ImGui_ImplSDL2_NewFrame(m_window); - ImGui_ImplDX11_NewFrame(); -#endif -} - -void D3D11HostDisplay::RenderDisplay() -{ -#if 0 - if (!m_display_texture_handle) - return; - - const auto [vp_left, vp_top, vp_width, vp_height] = - CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin); - - m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - m_context->VSSetShader(m_display_vertex_shader.Get(), nullptr, 0); - m_context->PSSetShader(m_display_pixel_shader.Get(), nullptr, 0); - m_context->PSSetShaderResources(0, 1, reinterpret_cast(&m_display_texture_handle)); - m_context->PSSetSamplers( - 0, 1, m_display_linear_filtering ? m_linear_sampler.GetAddressOf() : m_point_sampler.GetAddressOf()); - - const float uniforms[4] = { - static_cast(m_display_texture_view_x) / static_cast(m_display_texture_width), - static_cast(m_display_texture_view_y) / static_cast(m_display_texture_height), - (static_cast(m_display_texture_view_width) - 0.5f) / static_cast(m_display_texture_width), - (static_cast(m_display_texture_view_height) - 0.5f) / static_cast(m_display_texture_height)}; - const auto map = m_display_uniform_buffer.Map(m_context.Get(), sizeof(uniforms), sizeof(uniforms)); - std::memcpy(map.pointer, uniforms, sizeof(uniforms)); - m_display_uniform_buffer.Unmap(m_context.Get(), sizeof(uniforms)); - m_context->VSSetConstantBuffers(0, 1, m_display_uniform_buffer.GetD3DBufferArray()); - - const CD3D11_VIEWPORT vp(static_cast(vp_left), static_cast(vp_top), static_cast(vp_width), - static_cast(vp_height)); - m_context->RSSetViewports(1, &vp); - m_context->RSSetState(m_display_rasterizer_state.Get()); - m_context->OMSetDepthStencilState(m_display_depth_stencil_state.Get(), 0); - m_context->OMSetBlendState(m_display_blend_state.Get(), nullptr, 0xFFFFFFFFu); - - m_context->Draw(3, 0); -#endif -} diff --git a/src/duckstation-libretro/d3d11_host_display.h b/src/duckstation-libretro/d3d11_host_display.h deleted file mode 100644 index bf7589dd6..000000000 --- a/src/duckstation-libretro/d3d11_host_display.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#include "common/d3d11/staging_texture.h" -#include "common/d3d11/stream_buffer.h" -#include "common/d3d11/texture.h" -#include "common/windows_headers.h" -#include "core/host_display.h" -#include "libretro.h" -#include -#include -#include - -class D3D11HostDisplay final : public HostDisplay -{ -public: - template - using ComPtr = Microsoft::WRL::ComPtr; - - D3D11HostDisplay(ComPtr device, ComPtr context); - ~D3D11HostDisplay(); - - static bool RequestHardwareRendererContext(retro_hw_render_callback* cb); - - static std::unique_ptr Create(bool debug_device); - - RenderAPI GetRenderAPI() const override; - void* GetRenderDevice() const override; - void* GetRenderContext() const override; - - std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, - bool dynamic) override; - void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, - u32 data_stride) override; - bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) override; - - void SetVSync(bool enabled) override; - -private: - static constexpr u32 DISPLAY_UNIFORM_BUFFER_SIZE = 16; - - bool CreateD3DResources(); - - void Render() override; - void RenderDisplay(); - - ComPtr m_dxgi_factory; - - ComPtr m_device; - ComPtr m_context; - - ComPtr m_display_rasterizer_state; - ComPtr m_display_depth_stencil_state; - ComPtr m_display_blend_state; - ComPtr m_display_vertex_shader; - ComPtr m_display_pixel_shader; - ComPtr m_point_sampler; - ComPtr m_linear_sampler; - - D3D11::Texture m_display_pixels_texture; - D3D11::StreamBuffer m_display_uniform_buffer; - D3D11::AutoStagingTexture m_readback_staging_texture; -}; diff --git a/src/duckstation-libretro/duckstation-libretro.vcxproj b/src/duckstation-libretro/duckstation-libretro.vcxproj index def51a91a..f2296bb65 100644 --- a/src/duckstation-libretro/duckstation-libretro.vcxproj +++ b/src/duckstation-libretro/duckstation-libretro.vcxproj @@ -35,35 +35,35 @@ - - {bb08260f-6fbc-46af-8924-090ee71360c6} - {ee054e08-3799-4a59-a422-18259c105ffd} {868b98c8-65a1-494b-8346-250a73a48c0a} + + {6245dec8-d2da-47ee-a373-cbd6fcf3ece6} + {075ced82-6a20-46df-94c7-9624ac9ddbeb} - + - + - + - + {9D206548-DE8F-4D9D-A561-C7E5CD7A20DF} diff --git a/src/duckstation-libretro/duckstation-libretro.vcxproj.filters b/src/duckstation-libretro/duckstation-libretro.vcxproj.filters index 6875d06bb..daba5b448 100644 --- a/src/duckstation-libretro/duckstation-libretro.vcxproj.filters +++ b/src/duckstation-libretro/duckstation-libretro.vcxproj.filters @@ -3,18 +3,18 @@ - - + + - - + + \ No newline at end of file diff --git a/src/duckstation-libretro/libretro_d3d11_host_display.cpp b/src/duckstation-libretro/libretro_d3d11_host_display.cpp new file mode 100644 index 000000000..b153c90f4 --- /dev/null +++ b/src/duckstation-libretro/libretro_d3d11_host_display.cpp @@ -0,0 +1,116 @@ +#include "libretro_d3d11_host_display.h" +#include "common/align.h" +#include "common/assert.h" +#include "common/d3d11/shader_compiler.h" +#include "common/log.h" +#include "libretro_host_interface.h" +Log_SetChannel(D3D11HostDisplay); + +#define HAVE_D3D11 +#include "libretro_d3d.h" + +LibretroD3D11HostDisplay::LibretroD3D11HostDisplay() = default; + +LibretroD3D11HostDisplay::~LibretroD3D11HostDisplay() = default; + +void LibretroD3D11HostDisplay::SetVSync(bool enabled) +{ + // The libretro frontend controls this. + Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled)); +} + +bool LibretroD3D11HostDisplay::RequestHardwareRendererContext(retro_hw_render_callback* cb) +{ + cb->cache_context = true; + cb->bottom_left_origin = false; + cb->context_type = RETRO_HW_CONTEXT_DIRECT3D; + cb->version_major = 11; + cb->version_minor = 0; + + return g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER, cb); +} + +bool LibretroD3D11HostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, + bool debug_device) +{ + retro_hw_render_interface* ri = nullptr; + if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, &ri)) + { + Log_ErrorPrint("Failed to get HW render interface"); + return false; + } + else if (ri->interface_type != RETRO_HW_RENDER_INTERFACE_D3D11 || + ri->interface_version != RETRO_HW_RENDER_INTERFACE_D3D11_VERSION) + { + Log_ErrorPrint("Unexpected HW interface - type %u version %u", static_cast(ri->interface_type), + static_cast(ri->interface_version)); + return false; + } + + const retro_hw_render_interface_d3d11* d3d11_ri = reinterpret_cast(ri); + if (!d3d11_ri->device || !d3d11_ri->context) + { + Log_ErrorPrintf("Missing D3D device or context"); + return false; + } + + m_device = d3d11_ri->device; + m_context = d3d11_ri->context; + return CreateResources(); +} + +void LibretroD3D11HostDisplay::DestroyRenderDevice() +{ + DestroyResources(); + m_framebuffer.Destroy(); + m_context.Reset(); + m_device.Reset(); +} + +void LibretroD3D11HostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height) +{ + m_window_info.surface_width = static_cast(new_window_width); + m_window_info.surface_height = static_cast(new_window_height); +} + +bool LibretroD3D11HostDisplay::Render() +{ + // TODO: Skip framebuffer when offset is (0,0). + if (!CheckFramebufferSize(m_display_texture_width, m_display_texture_height)) + return false; + + // Ensure we're not currently bound. + ID3D11ShaderResourceView* null_srv = nullptr; + m_context->PSSetShaderResources(0, 1, &null_srv); + m_context->OMSetRenderTargets(1u, m_framebuffer.GetD3DRTVArray(), nullptr); + + if (HasDisplayTexture()) + { + RenderDisplay(0, 0, m_display_texture_width, m_display_texture_height, m_display_texture_handle, + m_display_texture_width, m_display_texture_height, m_display_texture_view_x, m_display_texture_view_y, + m_display_texture_view_width, m_display_texture_view_height, m_display_linear_filtering); + } + + if (HasSoftwareCursor()) + { + const auto [left, top, width, height] = CalculateSoftwareCursorDrawRect(); + RenderSoftwareCursor(left, top, width, height, m_cursor_texture.get()); + } + + // NOTE: libretro frontend expects the data bound to PS SRV slot 0. + m_context->OMSetRenderTargets(0, nullptr, nullptr); + m_context->PSSetShaderResources(0, 1, m_framebuffer.GetD3DSRVArray()); + g_retro_video_refresh_callback(RETRO_HW_FRAME_BUFFER_VALID, m_display_texture_width, m_display_texture_height, 0); + return true; +} + +bool LibretroD3D11HostDisplay::CheckFramebufferSize(u32 width, u32 height) +{ + if (m_framebuffer.GetWidth() >= width && m_framebuffer.GetHeight() >= height) + return true; + + const u32 rounded_width = Common::AlignUpPow2(width, 1024); + const u32 rounded_height = Common::AlignUpPow2(height, 512); + return m_framebuffer.Create(m_device.Get(), rounded_width, rounded_height, DXGI_FORMAT_R8G8B8A8_UNORM, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET); +} diff --git a/src/duckstation-libretro/libretro_d3d11_host_display.h b/src/duckstation-libretro/libretro_d3d11_host_display.h new file mode 100644 index 000000000..1c043a4d3 --- /dev/null +++ b/src/duckstation-libretro/libretro_d3d11_host_display.h @@ -0,0 +1,27 @@ +#pragma once +#include "common/d3d11/texture.h" +#include "frontend-common/d3d11_host_display.h" +#include "libretro.h" + +class LibretroD3D11HostDisplay final : public FrontendCommon::D3D11HostDisplay +{ +public: + LibretroD3D11HostDisplay(); + ~LibretroD3D11HostDisplay(); + + static bool RequestHardwareRendererContext(retro_hw_render_callback* cb); + + bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override; + void DestroyRenderDevice(); + + void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + + void SetVSync(bool enabled) override; + + bool Render() override; + +private: + bool CheckFramebufferSize(u32 width, u32 height); + + D3D11::Texture m_framebuffer; +}; diff --git a/src/duckstation-libretro/libretro_host_display.cpp b/src/duckstation-libretro/libretro_host_display.cpp index 90861830e..86646f8dd 100644 --- a/src/duckstation-libretro/libretro_host_display.cpp +++ b/src/duckstation-libretro/libretro_host_display.cpp @@ -1,8 +1,8 @@ #include "libretro_host_display.h" -#include "libretro_host_interface.h" #include "common/assert.h" #include "common/log.h" #include "libretro.h" +#include "libretro_host_interface.h" #include #include Log_SetChannel(LibretroHostDisplay); @@ -102,7 +102,52 @@ void* LibretroHostDisplay::GetRenderContext() const return nullptr; } -void LibretroHostDisplay::WindowResized(s32 new_window_width, s32 new_window_height) {} +bool LibretroHostDisplay::HasRenderDevice() const +{ + return true; +} + +bool LibretroHostDisplay::HasRenderSurface() const +{ + return true; +} + +bool LibretroHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) +{ + m_window_info = wi; + return true; +} + +bool LibretroHostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) +{ + return true; +} + +bool LibretroHostDisplay::MakeRenderContextCurrent() +{ + return true; +} + +bool LibretroHostDisplay::DoneRenderContextCurrent() +{ + return true; +} + +void LibretroHostDisplay::DestroyRenderDevice() {} + +void LibretroHostDisplay::DestroyRenderSurface() {} + +bool LibretroHostDisplay::ChangeRenderWindow(const WindowInfo& wi) +{ + m_window_info = wi; + return true; +} + +void LibretroHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height) +{ + m_window_info.surface_width = new_window_width; + m_window_info.surface_height = new_window_height; +} std::unique_ptr LibretroHostDisplay::CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic) @@ -123,28 +168,21 @@ bool LibretroHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 return true; } -void LibretroHostDisplay::SetVSync(bool enabled) {} - -void LibretroHostDisplay::Render() +void LibretroHostDisplay::SetVSync(bool enabled) { - if (m_display_texture_view_width != m_last_display_width || m_display_texture_view_height != m_last_display_height) - { - retro_game_geometry geom = {}; - geom.base_width = m_display_width; - geom.base_height = m_display_height; - geom.aspect_ratio = m_display_pixel_aspect_ratio; + // The libretro frontend controls this. + Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled)); +} - if (!g_retro_environment_callback(RETRO_ENVIRONMENT_SET_GEOMETRY, &geom)) - Log_WarningPrint("RETRO_ENVIRONMENT_SET_GEOMETRY failed"); - - m_last_display_width = m_display_texture_view_width; - m_last_display_height = m_display_texture_view_height; - } - - // TODO: padding... - if (m_display_texture_handle) +bool LibretroHostDisplay::Render() +{ + if (HasDisplayTexture()) { const LibretroDisplayTexture* tex = static_cast(m_display_texture_handle); - g_retro_video_refresh_callback(tex->GetData() + m_display_texture_view_y * tex->GetWidth() + m_display_texture_view_x, m_display_texture_view_width, m_display_texture_view_height , tex->GetDataPitch()); + g_retro_video_refresh_callback(tex->GetData() + m_display_texture_view_y * tex->GetWidth() + + m_display_texture_view_x, + m_display_texture_view_width, m_display_texture_view_height, tex->GetDataPitch()); } + + return true; } diff --git a/src/duckstation-libretro/libretro_host_display.h b/src/duckstation-libretro/libretro_host_display.h index 885ee766e..419377a62 100644 --- a/src/duckstation-libretro/libretro_host_display.h +++ b/src/duckstation-libretro/libretro_host_display.h @@ -11,7 +11,20 @@ public: RenderAPI GetRenderAPI() const override; void* GetRenderDevice() const override; void* GetRenderContext() const override; - void WindowResized(s32 new_window_width, s32 new_window_height) override; + + bool HasRenderDevice() const override; + bool HasRenderSurface() const override; + + bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override; + bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) override; + void DestroyRenderDevice() override; + + bool MakeRenderContextCurrent() override; + bool DoneRenderContextCurrent() override; + + bool ChangeRenderWindow(const WindowInfo& wi) override; + void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + void DestroyRenderSurface() override; std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic) override; @@ -22,9 +35,5 @@ public: void SetVSync(bool enabled) override; - void Render() override; - -private: - s32 m_last_display_width = -1; - s32 m_last_display_height = -1; + bool Render() override; }; diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index 414ed091f..d80218f92 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -1,16 +1,18 @@ #include "libretro_host_interface.h" #include "common/assert.h" +#include "common/byte_stream.h" #include "common/file_system.h" #include "common/log.h" #include "common/string_util.h" #include "core/analog_controller.h" #include "core/digital_controller.h" +#include "core/game_list.h" #include "core/gpu.h" #include "core/system.h" #include "libretro_audio_stream.h" #include "libretro_host_display.h" +#include "libretro_opengl_host_display.h" #include "libretro_settings_interface.h" -#include "opengl_host_display.h" #include #include #include @@ -19,18 +21,9 @@ Log_SetChannel(LibretroHostInterface); #ifdef WIN32 -#include "d3d11_host_display.h" +#include "libretro_d3d11_host_display.h" #endif -////////////////////////////////////////////////////////////////////////// -// TODO: -// - Fix up D3D11 -// - Save states -// - Expose the rest of the options -// - Memory card and controller settings -// - Better paths for memory cards/BIOS -////////////////////////////////////////////////////////////////////////// - LibretroHostInterface g_libretro_host_interface; retro_environment_t g_retro_environment_callback; @@ -40,16 +33,48 @@ retro_audio_sample_batch_t g_retro_audio_sample_batch_callback; retro_input_poll_t g_retro_input_poll_callback; retro_input_state_t g_retro_input_state_callback; +static retro_log_callback s_libretro_log_callback = {}; +static bool s_libretro_log_callback_valid = false; + +static const char* GetSaveDirectory() +{ + const char* save_directory = nullptr; + if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &save_directory) || !save_directory) + save_directory = "saves"; + + return save_directory; +} + +static void LibretroLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, + const char* message) +{ + static constexpr std::array levels = { + {RETRO_LOG_ERROR, RETRO_LOG_ERROR, RETRO_LOG_WARN, RETRO_LOG_INFO, RETRO_LOG_INFO, RETRO_LOG_INFO, RETRO_LOG_DEBUG, + RETRO_LOG_DEBUG, RETRO_LOG_DEBUG, RETRO_LOG_DEBUG}}; + + s_libretro_log_callback.log(levels[level], "[%s] %s\n", (level <= LOGLEVEL_PERF) ? functionName : channelName, + message); +} + LibretroHostInterface::LibretroHostInterface() = default; LibretroHostInterface::~LibretroHostInterface() = default; +void LibretroHostInterface::InitLogging() +{ + s_libretro_log_callback_valid = + g_retro_environment_callback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &s_libretro_log_callback); + if (s_libretro_log_callback_valid) + Log::RegisterCallback(LibretroLogCallback, nullptr); +} + bool LibretroHostInterface::Initialize() { if (!HostInterface::Initialize()) return false; LoadSettings(); + UpdateLogging(); return true; } @@ -60,11 +85,12 @@ void LibretroHostInterface::Shutdown() void LibretroHostInterface::ReportError(const char* message) { - Log_ErrorPrint(message); + AddFormattedOSDMessage(60.0f, "ERROR: %s", message); } void LibretroHostInterface::ReportMessage(const char* message) { + AddOSDMessage(message, 10.0f); Log_InfoPrint(message); } @@ -74,8 +100,44 @@ bool LibretroHostInterface::ConfirmMessage(const char* message) return false; } +void LibretroHostInterface::GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) +{ + // Just use the filename for now... we don't have the game list. Unless we can pull this from the frontend somehow? + *title = GameList::GetTitleForPath(path); + code->clear(); +} + +std::string LibretroHostInterface::GetSharedMemoryCardPath(u32 slot) const +{ + return GetUserDirectoryRelativePath("%s/shared_card_%d.mcd", GetSaveDirectory(), slot + 1); +} + +std::string LibretroHostInterface::GetGameMemoryCardPath(const char* game_code, u32 slot) const +{ + return GetUserDirectoryRelativePath("%s/%s_%d.mcd", GetSaveDirectory(), game_code, slot + 1); +} + +void LibretroHostInterface::AddOSDMessage(std::string message, float duration /*= 2.0f*/) +{ + retro_message msg = {}; + msg.msg = message.c_str(); + msg.frames = static_cast(duration * (m_system ? m_system->GetThrottleFrequency() : 60.0f)); + g_retro_environment_callback(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); +} + void LibretroHostInterface::retro_get_system_av_info(struct retro_system_av_info* info) { + const bool use_resolution_scale = (m_settings.gpu_renderer != GPURenderer::Software); + GetSystemAVInfo(info, use_resolution_scale); + + Log_InfoPrintf("base = %ux%u, max = %ux%u, aspect ratio = %.2f, fps = %.2f", info->geometry.base_width, + info->geometry.base_height, info->geometry.max_width, info->geometry.max_height, + info->geometry.aspect_ratio, info->timing.fps); +} + +void LibretroHostInterface::GetSystemAVInfo(struct retro_system_av_info* info, bool use_resolution_scale) +{ + const u32 resolution_scale = use_resolution_scale ? m_settings.gpu_resolution_scale : 1u; Assert(m_system); std::memset(info, 0, sizeof(*info)); @@ -93,22 +155,66 @@ void LibretroHostInterface::retro_get_system_av_info(struct retro_system_av_info info->geometry.base_height = 576; } - info->geometry.max_width = 1024; - info->geometry.max_height = 512; + info->geometry.max_width = 1024 * resolution_scale; + info->geometry.max_height = 512 * resolution_scale; info->timing.fps = m_system->GetThrottleFrequency(); info->timing.sample_rate = static_cast(AUDIO_SAMPLE_RATE); } +void LibretroHostInterface::UpdateSystemAVInfo(bool use_resolution_scale) +{ + struct retro_system_av_info avi; + GetSystemAVInfo(&avi, use_resolution_scale); + + Log_InfoPrintf("base = %ux%u, max = %ux%u, aspect ratio = %.2f, fps = %.2f", avi.geometry.base_width, + avi.geometry.base_height, avi.geometry.max_width, avi.geometry.max_height, avi.geometry.aspect_ratio, + avi.timing.fps); + + if (!g_retro_environment_callback(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avi)) + Log_ErrorPrintf("Failed to update system AV info on resolution change"); +} + +void LibretroHostInterface::UpdateGeometry() +{ + struct retro_system_av_info avi; + const bool use_resolution_scale = (m_settings.gpu_renderer != GPURenderer::Software); + GetSystemAVInfo(&avi, use_resolution_scale); + + Log_InfoPrintf("base = %ux%u, max = %ux%u, aspect ratio = %.2f", avi.geometry.base_width, avi.geometry.base_height, + avi.geometry.max_width, avi.geometry.max_height, avi.geometry.aspect_ratio); + + if (!g_retro_environment_callback(RETRO_ENVIRONMENT_SET_GEOMETRY, &avi.geometry)) + Log_WarningPrint("RETRO_ENVIRONMENT_SET_GEOMETRY failed"); +} + +void LibretroHostInterface::UpdateLogging() +{ + Log::SetFilterLevel(m_settings.log_level); + + if (s_libretro_log_callback_valid) + Log::SetConsoleOutputParams(false); + else + Log::SetConsoleOutputParams(true, nullptr, m_settings.log_level); +} + bool LibretroHostInterface::retro_load_game(const struct retro_game_info* game) { SystemBootParameters bp; bp.filename = game->path; + bp.force_software_renderer = !m_hw_render_callback_valid; if (!BootSystem(bp)) return false; - RequestHardwareRendererContext(); + if (m_settings.gpu_renderer != GPURenderer::Software) + { + if (!m_hw_render_callback_valid) + RequestHardwareRendererContext(); + else + SwitchToHardwareRenderer(); + } + return true; } @@ -116,6 +222,9 @@ void LibretroHostInterface::retro_run_frame() { Assert(m_system); + if (HasCoreVariablesChanged()) + UpdateSettings(); + UpdateControllers(); m_system->GetGPU()->RestoreGraphicsAPIState(); @@ -132,17 +241,46 @@ unsigned LibretroHostInterface::retro_get_region() return m_system->IsPALRegion() ? RETRO_REGION_PAL : RETRO_REGION_NTSC; } +size_t LibretroHostInterface::retro_serialize_size() +{ + return System::MAX_SAVE_STATE_SIZE; +} + +bool LibretroHostInterface::retro_serialize(void* data, size_t size) +{ + std::unique_ptr stream = ByteStream_CreateMemoryStream(data, static_cast(size)); + if (!m_system->SaveState(stream.get(), 0)) + { + Log_ErrorPrintf("Failed to save state to memory stream"); + return false; + } + + return true; +} + +bool LibretroHostInterface::retro_unserialize(const void* data, size_t size) +{ + std::unique_ptr stream = ByteStream_CreateReadOnlyMemoryStream(data, static_cast(size)); + if (!m_system->LoadState(stream.get())) + { + Log_ErrorPrintf("Failed to load save state from memory stream"); + return false; + } + + return true; +} + bool LibretroHostInterface::AcquireHostDisplay() { // start in software mode, switch to hardware later - m_display = new LibretroHostDisplay(); + m_display = std::make_unique(); return true; } void LibretroHostInterface::ReleaseHostDisplay() { - delete m_display; - m_display = nullptr; + m_display->DestroyRenderDevice(); + m_display.reset(); } std::unique_ptr LibretroHostInterface::CreateAudioStream(AudioBackend backend) @@ -150,7 +288,13 @@ std::unique_ptr LibretroHostInterface::CreateAudioStream(AudioBacke return std::make_unique(); } -static std::array s_option_definitions = {{ +void LibretroHostInterface::OnSystemDestroyed() +{ + HostInterface::OnSystemDestroyed(); + m_using_hardware_renderer = false; +} + +static std::array s_option_definitions = {{ {"Console.Region", "Console Region", "Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.", @@ -188,7 +332,12 @@ static std::array s_option_definitions = {{ #endif {"OpenGL", "Hardware (OpenGL)"}, {"Software", "Software"}}, - "OpenGL"}, +#ifdef WIN32 + "D3D11" +#else + "OpenGL" +#endif + }, {"GPU.ResolutionScale", "Rendering Resolution Scale", "Scales internal rendering resolution by the specified multiplier. Larger values are slower. Some games require " @@ -234,8 +383,63 @@ static std::array s_option_definitions = {{ {"Display.AspectRatio", "Aspect Ratio", "Sets the core-provided aspect ratio.", - {{"4:3", "4:3"}, {"16:9", "16:9"}, {"1:1", "1:1"}}, + {{"4:3", "4:3"}, {"16:9", "16:9"}, {"2:1", "2:1 (VRAM 1:1)"}, {"1:1", "1:1"}}, "4:3"}, + {"MemoryCards.LoadFromSaveStates", + "Load Memory Cards From Save States", + "Sets whether the contents of memory cards will be loaded when a save state is loaded.", + {{"true", "Enabled"}, {"false", "Disabled"}}, + "false"}, + {"MemoryCards.Card1Type", + "Memory Card 1 Type", + "Sets the type of memory card for Slot 1.", + {{"None", "No Memory Card"}, + {"Shared", "Shared Between All Games"}, + {"PerGame", "Separate Card Per Game (Game Code)"}, + {"PerGameTitle", "Separate Card Per Game (Game Title)"}}, + "PerGameTitle"}, + {"MemoryCards.Card2Type", + "Memory Card 2 Type", + "Sets the type of memory card for Slot 2.", + {{"None", "No Memory Card"}, + {"Shared", "Shared Between All Games"}, + {"PerGame", "Separate Card Per Game (Game Code)"}, + {"PerGameTitle", "Separate Card Per Game (Game Title)"}}, + "None"}, + {"Controller1.Type", + "Controller 1 Type", + "Sets the type of controller for Slot 1.", + {{"None", "None"}, + {"DigitalController", "Digital Controller"}, + {"AnalogController", "Analog Controller (DualShock)"}, + {"NamcoGunCon", "Namco GunCon"}, + {"PlayStationMouse", "PlayStation Mouse"}, + {"NeGcon", "NeGcon"}}, + "DigitalController"}, + {"Controller2.Type", + "Controller 2 Type", + "Sets the type of controller for Slot 2.", + {{"None", "None"}, + {"DigitalController", "Digital Controller"}, + {"AnalogController", "Analog Controller (DualShock)"}, + {"NamcoGunCon", "Namco GunCon"}, + {"PlayStationMouse", "PlayStation Mouse"}, + {"NeGcon", "NeGcon"}}, + "None"}, + {"Logging.LogLevel", + "Log Level", + "Sets the level of information logged by the core.", + {{"None", "None"}, + {"Error", "Error"}, + {"Warning", "Warning"}, + {"Perf", "Performance"}, + {"Success", "Success"}, + {"Info", "Information"}, + {"Dev", "Developer"}, + {"Profile", "Profile"}, + {"Debug", "Debug"}, + {"Trace", "Trace"}}, + "Info"}, {}, }}; @@ -252,39 +456,78 @@ bool LibretroHostInterface::SetCoreOptions() return false; } +bool LibretroHostInterface::HasCoreVariablesChanged() +{ + bool changed = false; + return (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &changed) && changed); +} + void LibretroHostInterface::LoadSettings() { LibretroSettingsInterface si; m_settings.Load(si); - // Overrides - m_settings.log_level = LOGLEVEL_DEV; - m_settings.log_to_console = true; - - // start in software, switch later - m_settings.gpu_renderer = GPURenderer::Software; - // Assume BIOS files are located in system directory. const char* system_directory = nullptr; if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_directory) || !system_directory) system_directory = "bios"; m_settings.bios_path = StringUtil::StdStringFromFormat("%s%cscph1001.bin", system_directory, FS_OSPATH_SEPERATOR_CHARACTER); - - // TODOs - expose via config - m_settings.controller_types[0] = ControllerType::DigitalController; - m_settings.controller_types[1] = ControllerType::None; - m_settings.memory_card_types[0] = MemoryCardType::None; - m_settings.memory_card_types[1] = MemoryCardType::None; } void LibretroHostInterface::UpdateSettings() { Settings old_settings(std::move(m_settings)); LoadSettings(); + + if (m_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale && + m_settings.gpu_renderer != GPURenderer::Software) + { + ReportMessage("Resolution changed, updating system AV info..."); + + // this will probably recreate the device... so save the state first by switching to software + if (m_using_hardware_renderer) + SwitchToSoftwareRenderer(); + + UpdateSystemAVInfo(true); + + if (!m_hw_render_callback_valid) + RequestHardwareRendererContext(); + else if (!m_using_hardware_renderer) + SwitchToHardwareRenderer(); + + // Don't let the base class mess with the GPU. + old_settings.gpu_resolution_scale = m_settings.gpu_resolution_scale; + } + + if (m_settings.gpu_renderer != old_settings.gpu_renderer) + { + ReportFormattedMessage("Switching to %s renderer...", Settings::GetRendererDisplayName(m_settings.gpu_renderer)); + + if (m_using_hardware_renderer) + SwitchToSoftwareRenderer(); + + if (m_settings.gpu_renderer != GPURenderer::Software) + RequestHardwareRendererContext(); + + // Don't let the base class recreate the GPU or system. + old_settings.gpu_renderer = m_settings.gpu_renderer; + } + CheckForSettingsChanges(old_settings); } +void LibretroHostInterface::CheckForSettingsChanges(const Settings& old_settings) +{ + HostInterface::CheckForSettingsChanges(old_settings); + + if (m_settings.display_aspect_ratio != old_settings.display_aspect_ratio) + UpdateGeometry(); + + if (m_settings.log_level != old_settings.log_level) + UpdateLogging(); +} + void LibretroHostInterface::UpdateControllers() { g_retro_input_poll_callback(); @@ -339,26 +582,42 @@ void LibretroHostInterface::UpdateControllersDigitalController(u32 index) bool LibretroHostInterface::RequestHardwareRendererContext() { GPURenderer renderer = Settings::DEFAULT_GPU_RENDERER; - retro_variable renderer_variable{"GPU.Renderer", "OpenGL"}; + retro_variable renderer_variable{"GPU.Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)}; if (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_VARIABLE, &renderer_variable) && renderer_variable.value) renderer = Settings::ParseRendererName(renderer_variable.value).value_or(Settings::DEFAULT_GPU_RENDERER); + Log_InfoPrintf("Renderer = %s", Settings::GetRendererName(renderer)); if (renderer == GPURenderer::Software) - return true; + { + m_hw_render_callback_valid = false; + return false; + } + + Log_InfoPrintf("Requesting hardware renderer context for %s", Settings::GetRendererName(renderer)); m_hw_render_callback = {}; m_hw_render_callback.context_reset = HardwareRendererContextReset; m_hw_render_callback.context_destroy = HardwareRendererContextDestroy; + switch (renderer) + { #ifdef WIN32 - if (renderer == GPURenderer::HardwareD3D11 && false) - return D3D11HostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); + case GPURenderer::HardwareD3D11: + m_hw_render_callback_valid = LibretroD3D11HostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); + break; #endif - if (renderer == GPURenderer::HardwareOpenGL) - return OpenGLHostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); + case GPURenderer::HardwareOpenGL: + m_hw_render_callback_valid = LibretroOpenGLHostDisplay::RequestHardwareRendererContext(&m_hw_render_callback); + break; - return false; + default: + Log_ErrorPrintf("Unhandled renderer %s", Settings::GetRendererName(renderer)); + m_hw_render_callback_valid = false; + break; + } + + return m_hw_render_callback_valid; } void LibretroHostInterface::HardwareRendererContextReset() @@ -366,23 +625,25 @@ void LibretroHostInterface::HardwareRendererContextReset() Log_InfoPrintf("Hardware context reset, type = %u", static_cast(g_libretro_host_interface.m_hw_render_callback.context_type)); - std::unique_ptr new_display = nullptr; - GPURenderer new_renderer = GPURenderer::Software; + g_libretro_host_interface.m_hw_render_callback_valid = true; + g_libretro_host_interface.SwitchToHardwareRenderer(); +} +void LibretroHostInterface::SwitchToHardwareRenderer() +{ + std::unique_ptr display = nullptr; switch (g_libretro_host_interface.m_hw_render_callback.context_type) { case RETRO_HW_CONTEXT_OPENGL: case RETRO_HW_CONTEXT_OPENGL_CORE: case RETRO_HW_CONTEXT_OPENGLES3: case RETRO_HW_CONTEXT_OPENGLES_VERSION: - new_display = OpenGLHostDisplay::Create(g_libretro_host_interface.m_settings.gpu_use_debug_device); - new_renderer = GPURenderer::HardwareOpenGL; + display = std::make_unique(); break; #ifdef WIN32 case RETRO_HW_CONTEXT_DIRECT3D: - new_display = D3D11HostDisplay::Create(g_libretro_host_interface.m_settings.gpu_use_debug_device); - new_renderer = GPURenderer::HardwareD3D11; + display = std::make_unique(); break; #endif @@ -390,25 +651,43 @@ void LibretroHostInterface::HardwareRendererContextReset() break; } - if (!new_display) + struct retro_system_av_info avi; + g_libretro_host_interface.GetSystemAVInfo(&avi, true); + + WindowInfo wi; + wi.type = WindowInfo::Type::Libretro; + wi.display_connection = &g_libretro_host_interface.m_hw_render_callback; + wi.surface_width = avi.geometry.base_width; + wi.surface_height = avi.geometry.base_height; + if (!display || !display->CreateRenderDevice(wi, {}, g_libretro_host_interface.m_settings.gpu_use_debug_device)) { Log_ErrorPrintf("Failed to create hardware host display"); return; } - HostDisplay* old_display = g_libretro_host_interface.m_display; - g_libretro_host_interface.m_display = new_display.release(); - g_libretro_host_interface.m_settings.gpu_renderer = new_renderer; - g_libretro_host_interface.m_system->RecreateGPU(new_renderer); - delete old_display; + std::swap(display, g_libretro_host_interface.m_display); + g_libretro_host_interface.m_system->RecreateGPU(g_libretro_host_interface.m_settings.gpu_renderer); + display->DestroyRenderDevice(); + m_using_hardware_renderer = true; } void LibretroHostInterface::HardwareRendererContextDestroy() { + g_libretro_host_interface.m_hw_render_callback_valid = false; + // switch back to software - HostDisplay* old_display = g_libretro_host_interface.m_display; - g_libretro_host_interface.m_display = new LibretroHostDisplay(); - g_libretro_host_interface.m_settings.gpu_renderer = GPURenderer::Software; - g_libretro_host_interface.m_system->RecreateGPU(GPURenderer::Software); - delete old_display; + if (g_libretro_host_interface.m_using_hardware_renderer) + { + Log_InfoPrintf("Lost hardware renderer context, switching to software renderer"); + g_libretro_host_interface.SwitchToSoftwareRenderer(); + } +} + +void LibretroHostInterface::SwitchToSoftwareRenderer() +{ + std::unique_ptr display = std::make_unique(); + std::swap(display, g_libretro_host_interface.m_display); + g_libretro_host_interface.m_system->RecreateGPU(GPURenderer::Software); + display->DestroyRenderDevice(); + m_using_hardware_renderer = false; } diff --git a/src/duckstation-libretro/libretro_host_interface.h b/src/duckstation-libretro/libretro_host_interface.h index e47b522fd..1296efca6 100644 --- a/src/duckstation-libretro/libretro_host_interface.h +++ b/src/duckstation-libretro/libretro_host_interface.h @@ -9,7 +9,9 @@ public: LibretroHostInterface(); ~LibretroHostInterface() override; + static void InitLogging(); static bool SetCoreOptions(); + static bool HasCoreVariablesChanged(); bool Initialize() override; void Shutdown() override; @@ -17,33 +19,49 @@ public: void ReportError(const char* message) override; void ReportMessage(const char* message) override; bool ConfirmMessage(const char* message) override; + void AddOSDMessage(std::string message, float duration = 2.0f) override; - const retro_hw_render_callback& GetHWRenderCallback() const { return m_hw_render_callback; } + void GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) override; + std::string GetSharedMemoryCardPath(u32 slot) const override; + std::string GetGameMemoryCardPath(const char* game_code, u32 slot) const override; // Called by frontend void retro_get_system_av_info(struct retro_system_av_info* info); bool retro_load_game(const struct retro_game_info* game); void retro_run_frame(); unsigned retro_get_region(); + size_t retro_serialize_size(); + bool retro_serialize(void* data, size_t size); + bool retro_unserialize(const void* data, size_t size); protected: bool AcquireHostDisplay() override; void ReleaseHostDisplay() override; std::unique_ptr CreateAudioStream(AudioBackend backend) override; + void OnSystemDestroyed() override; + void CheckForSettingsChanges(const Settings& old_settings) override; private: void LoadSettings(); void UpdateSettings(); void UpdateControllers(); void UpdateControllersDigitalController(u32 index); + void GetSystemAVInfo(struct retro_system_av_info* info, bool use_resolution_scale); + void UpdateSystemAVInfo(bool use_resolution_scale); + void UpdateGeometry(); + void UpdateLogging(); // Hardware renderer setup. bool RequestHardwareRendererContext(); + void SwitchToHardwareRenderer(); + void SwitchToSoftwareRenderer(); static void HardwareRendererContextReset(); static void HardwareRendererContextDestroy(); retro_hw_render_callback m_hw_render_callback = {}; + bool m_hw_render_callback_valid = false; + bool m_using_hardware_renderer = false; }; extern LibretroHostInterface g_libretro_host_interface; diff --git a/src/duckstation-libretro/libretro_opengl_host_display.cpp b/src/duckstation-libretro/libretro_opengl_host_display.cpp new file mode 100644 index 000000000..080d4fbbd --- /dev/null +++ b/src/duckstation-libretro/libretro_opengl_host_display.cpp @@ -0,0 +1,143 @@ +#include "libretro_opengl_host_display.h" +#include "common/assert.h" +#include "common/log.h" +#include "core/gpu.h" +#include "libretro.h" +#include "libretro_host_interface.h" +#include +#include +Log_SetChannel(LibretroOpenGLHostDisplay); + +LibretroOpenGLHostDisplay::LibretroOpenGLHostDisplay() = default; + +LibretroOpenGLHostDisplay::~LibretroOpenGLHostDisplay() = default; + +HostDisplay::RenderAPI LibretroOpenGLHostDisplay::GetRenderAPI() const +{ + return m_is_gles ? HostDisplay::RenderAPI::OpenGLES : HostDisplay::RenderAPI::OpenGL; +} + +void LibretroOpenGLHostDisplay::SetVSync(bool enabled) +{ + // The libretro frontend controls this. + Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled)); +} + +bool LibretroOpenGLHostDisplay::RequestHardwareRendererContext(retro_hw_render_callback* cb) +{ + // Prefer a desktop OpenGL context where possible. If we can't get this, try OpenGL ES. + static constexpr std::array, 11> desktop_versions_to_try = { + {/*{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, {3, 2}, */ {3, 1}, {3, 0}}}; + static constexpr std::array, 4> es_versions_to_try = {{{3, 2}, {3, 1}, {3, 0}}}; + + cb->cache_context = true; + cb->bottom_left_origin = true; + + for (const auto& [major, minor] : desktop_versions_to_try) + { + if (major > 3 || (major == 3 && minor >= 2)) + { + cb->context_type = RETRO_HW_CONTEXT_OPENGL_CORE; + cb->version_major = major; + cb->version_minor = minor; + } + else + { + cb->context_type = RETRO_HW_CONTEXT_OPENGL; + cb->version_major = 0; + cb->version_minor = 0; + } + + if (g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER, cb)) + return true; + } + + for (const auto& [major, minor] : es_versions_to_try) + { + if (major >= 3 && minor > 0) + { + cb->context_type = RETRO_HW_CONTEXT_OPENGLES_VERSION; + cb->version_major = major; + cb->version_minor = minor; + } + else + { + cb->context_type = RETRO_HW_CONTEXT_OPENGLES3; + cb->version_major = 0; + cb->version_minor = 0; + } + + if (g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER, cb)) + return true; + } + + Log_ErrorPrint("Failed to set any GL HW renderer"); + return false; +} + +bool LibretroOpenGLHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, + bool debug_device) +{ + Assert(wi.type == WindowInfo::Type::Libretro); + + // gross - but can't do much because of the GLADloadproc below. + static retro_hw_render_callback* cb; + cb = static_cast(wi.display_connection); + + m_window_info = wi; + m_is_gles = (cb->context_type == RETRO_HW_CONTEXT_OPENGLES3 || cb->context_type == RETRO_HW_CONTEXT_OPENGLES_VERSION); + + const GLADloadproc get_proc_address = [](const char* sym) -> void* { + return reinterpret_cast(cb->get_proc_address(sym)); + }; + + // Load GLAD. + const auto load_result = m_is_gles ? gladLoadGLES2Loader(get_proc_address) : gladLoadGLLoader(get_proc_address); + if (!load_result) + { + Log_ErrorPrintf("Failed to load GL functions"); + return false; + } + + return CreateResources(); +} + +void LibretroOpenGLHostDisplay::DestroyRenderDevice() +{ + DestroyResources(); +} + +void LibretroOpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height) +{ + m_window_info.surface_width = static_cast(new_window_width); + m_window_info.surface_height = static_cast(new_window_height); +} + +bool LibretroOpenGLHostDisplay::Render() +{ + const GLuint fbo = static_cast( + static_cast(m_window_info.display_connection)->get_current_framebuffer()); + + glDisable(GL_SCISSOR_TEST); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + + if (HasDisplayTexture()) + { + RenderDisplay(0, 0, m_display_texture_width, m_display_texture_height, m_display_texture_handle, + m_display_texture_width, m_display_texture_height, m_display_texture_view_x, m_display_texture_view_y, + m_display_texture_view_width, m_display_texture_view_height, m_display_linear_filtering); + } + + if (HasSoftwareCursor()) + { + const auto [left, top, width, height] = CalculateSoftwareCursorDrawRect(); + RenderSoftwareCursor(left, top, width, height, m_cursor_texture.get()); + } + + g_retro_video_refresh_callback(RETRO_HW_FRAME_BUFFER_VALID, m_display_texture_width, m_display_texture_height, 0); + + GL::Program::ResetLastProgram(); + return true; +} diff --git a/src/duckstation-libretro/libretro_opengl_host_display.h b/src/duckstation-libretro/libretro_opengl_host_display.h new file mode 100644 index 000000000..b6aa77440 --- /dev/null +++ b/src/duckstation-libretro/libretro_opengl_host_display.h @@ -0,0 +1,31 @@ +#pragma once +#include "common/gl/program.h" +#include "common/gl/texture.h" +#include "core/host_display.h" +#include "frontend-common/opengl_host_display.h" +#include "libretro.h" +#include +#include + +class LibretroOpenGLHostDisplay final : public FrontendCommon::OpenGLHostDisplay +{ +public: + LibretroOpenGLHostDisplay(); + ~LibretroOpenGLHostDisplay(); + + static bool RequestHardwareRendererContext(retro_hw_render_callback* cb); + + RenderAPI GetRenderAPI() const override; + + bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override; + void DestroyRenderDevice(); + + void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + + void SetVSync(bool enabled) override; + + bool Render() override; + +private: + bool m_is_gles = false; +}; diff --git a/src/duckstation-libretro/main.cpp b/src/duckstation-libretro/main.cpp index fe0ee9b03..56b1e00da 100644 --- a/src/duckstation-libretro/main.cpp +++ b/src/duckstation-libretro/main.cpp @@ -11,9 +11,8 @@ RETRO_API unsigned retro_api_version(void) RETRO_API void retro_init(void) { - Log::SetConsoleOutputParams(true); - Log::SetDebugOutputParams(true); - Log_InfoPrintf("retro_init()"); + // default log to stdout until we get an interface + Log::SetConsoleOutputParams(true, nullptr, LOGLEVEL_INFO); if (!g_libretro_host_interface.Initialize()) Panic("Host interface initialization failed"); @@ -21,7 +20,6 @@ RETRO_API void retro_init(void) RETRO_API void retro_deinit(void) { - Log_InfoPrintf("retro_deinit()"); g_libretro_host_interface.Shutdown(); } @@ -66,20 +64,17 @@ RETRO_API void retro_run(void) RETRO_API size_t retro_serialize_size(void) { - Log_ErrorPrintf("retro_serialize_size()"); - return 0; + return g_libretro_host_interface.retro_serialize_size(); } RETRO_API bool retro_serialize(void* data, size_t size) { - Log_ErrorPrintf("retro_serialize()"); - return false; + return g_libretro_host_interface.retro_serialize(data, size); } RETRO_API bool retro_unserialize(const void* data, size_t size) { - Log_ErrorPrintf("retro_unserialize()"); - return false; + return g_libretro_host_interface.retro_unserialize(data, size); } RETRO_API void retro_cheat_reset(void) @@ -106,7 +101,6 @@ RETRO_API bool retro_load_game_special(unsigned game_type, const struct retro_ga RETRO_API void retro_unload_game(void) { - Log_ErrorPrintf("retro_unload_game()"); g_libretro_host_interface.DestroySystem(); } @@ -133,6 +127,7 @@ RETRO_API void retro_set_environment(retro_environment_t f) if (!core_options_set) { core_options_set = true; + g_libretro_host_interface.InitLogging(); if (!g_libretro_host_interface.SetCoreOptions()) Log_WarningPrintf("Failed to set core options, settings will not be changeable."); } diff --git a/src/duckstation-libretro/opengl_host_display.cpp b/src/duckstation-libretro/opengl_host_display.cpp deleted file mode 100644 index fca833947..000000000 --- a/src/duckstation-libretro/opengl_host_display.cpp +++ /dev/null @@ -1,385 +0,0 @@ -#include "opengl_host_display.h" -#include "common/assert.h" -#include "common/log.h" -#include "libretro.h" -#include "libretro_host_interface.h" -#include -#include -Log_SetChannel(OpenGLHostDisplay); - -class OpenGLDisplayWidgetTexture : public HostDisplayTexture -{ -public: - OpenGLDisplayWidgetTexture(GLuint id, u32 width, u32 height) : m_id(id), m_width(width), m_height(height) {} - ~OpenGLDisplayWidgetTexture() override { glDeleteTextures(1, &m_id); } - - void* GetHandle() const override { return reinterpret_cast(static_cast(m_id)); } - u32 GetWidth() const override { return m_width; } - u32 GetHeight() const override { return m_height; } - - GLuint GetGLID() const { return m_id; } - - static std::unique_ptr Create(u32 width, u32 height, const void* initial_data, - u32 initial_data_stride) - { - GLuint id; - glGenTextures(1, &id); - - GLint old_texture_binding = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding); - - // TODO: Set pack width - Assert(!initial_data || initial_data_stride == (width * sizeof(u32))); - - glBindTexture(GL_TEXTURE_2D, id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, initial_data); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glBindTexture(GL_TEXTURE_2D, id); - return std::make_unique(id, width, height); - } - -private: - GLuint m_id; - u32 m_width; - u32 m_height; -}; - -OpenGLHostDisplay::OpenGLHostDisplay(bool is_gles) : m_is_gles(is_gles) {} - -OpenGLHostDisplay::~OpenGLHostDisplay() -{ - if (m_display_vao != 0) - glDeleteVertexArrays(1, &m_display_vao); - if (m_display_linear_sampler != 0) - glDeleteSamplers(1, &m_display_linear_sampler); - if (m_display_nearest_sampler != 0) - glDeleteSamplers(1, &m_display_nearest_sampler); - - m_display_program.Destroy(); -} - -HostDisplay::RenderAPI OpenGLHostDisplay::GetRenderAPI() const -{ - return m_is_gles ? HostDisplay::RenderAPI::OpenGLES : HostDisplay::RenderAPI::OpenGL; -} - -void* OpenGLHostDisplay::GetRenderDevice() const -{ - return nullptr; -} - -void* OpenGLHostDisplay::GetRenderContext() const -{ - return nullptr; -} - -std::unique_ptr OpenGLHostDisplay::CreateTexture(u32 width, u32 height, const void* data, - u32 data_stride, bool dynamic) -{ - return OpenGLDisplayWidgetTexture::Create(width, height, data, data_stride); -} - -void OpenGLHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, - const void* data, u32 data_stride) -{ - OpenGLDisplayWidgetTexture* tex = static_cast(texture); - Assert((data_stride % sizeof(u32)) == 0); - - GLint old_texture_binding = 0, old_alignment = 0, old_row_length = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding); - glGetIntegerv(GL_UNPACK_ALIGNMENT, &old_alignment); - glGetIntegerv(GL_UNPACK_ROW_LENGTH, &old_row_length); - - glBindTexture(GL_TEXTURE_2D, tex->GetGLID()); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glPixelStorei(GL_UNPACK_ROW_LENGTH, data_stride / sizeof(u32)); - - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); - - glPixelStorei(GL_UNPACK_ALIGNMENT, old_alignment); - glPixelStorei(GL_UNPACK_ROW_LENGTH, old_row_length); - glBindTexture(GL_TEXTURE_2D, old_texture_binding); -} - -bool OpenGLHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) -{ - GLint old_alignment = 0, old_row_length = 0; - glGetIntegerv(GL_PACK_ALIGNMENT, &old_alignment); - glGetIntegerv(GL_PACK_ROW_LENGTH, &old_row_length); - glPixelStorei(GL_PACK_ALIGNMENT, sizeof(u32)); - glPixelStorei(GL_PACK_ROW_LENGTH, out_data_stride / sizeof(u32)); - - const GLuint texture = static_cast(reinterpret_cast(texture_handle)); - GL::Texture::GetTextureSubImage(texture, 0, x, y, 0, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, - height * out_data_stride, out_data); - - glPixelStorei(GL_PACK_ALIGNMENT, old_alignment); - glPixelStorei(GL_PACK_ROW_LENGTH, old_row_length); - return true; -} - -void OpenGLHostDisplay::SetVSync(bool enabled) -{ - // TODO -} - -const char* OpenGLHostDisplay::GetGLSLVersionString() const -{ - if (m_is_gles) - { - if (GLAD_GL_ES_VERSION_3_0) - return "#version 300 es"; - else - return "#version 100"; - } - else - { - if (GLAD_GL_VERSION_3_3) - return "#version 330"; - else - return "#version 130"; - } -} - -std::string OpenGLHostDisplay::GetGLSLVersionHeader() const -{ - std::string header = GetGLSLVersionString(); - header += "\n\n"; - if (m_is_gles) - { - header += "precision highp float;\n"; - header += "precision highp int;\n\n"; - } - - return header; -} - -static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar* message, const void* userParam) -{ - switch (severity) - { - case GL_DEBUG_SEVERITY_HIGH_KHR: - Log_ErrorPrintf(message); - break; - case GL_DEBUG_SEVERITY_MEDIUM_KHR: - Log_WarningPrint(message); - break; - case GL_DEBUG_SEVERITY_LOW_KHR: - Log_InfoPrintf(message); - break; - case GL_DEBUG_SEVERITY_NOTIFICATION: - // Log_DebugPrint(message); - break; - } -} - -bool OpenGLHostDisplay::RequestHardwareRendererContext(retro_hw_render_callback* cb) -{ - // Prefer a desktop OpenGL context where possible. If we can't get this, try OpenGL ES. - static constexpr std::array, 11> desktop_versions_to_try = { - {/*{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, {3, 2}, */ {3, 1}, {3, 0}}}; - static constexpr std::array, 4> es_versions_to_try = {{{3, 2}, {3, 1}, {3, 0}}}; - - cb->cache_context = true; - cb->bottom_left_origin = true; - - for (const auto& [major, minor] : desktop_versions_to_try) - { - if (major > 3 || (major == 3 && minor >= 2)) - { - cb->context_type = RETRO_HW_CONTEXT_OPENGL_CORE; - cb->version_major = major; - cb->version_minor = minor; - } - else - { - cb->context_type = RETRO_HW_CONTEXT_OPENGL; - cb->version_major = 0; - cb->version_minor = 0; - } - - if (g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER, cb)) - return true; - } - - for (const auto& [major, minor] : es_versions_to_try) - { - if (major >= 3 && minor > 0) - { - cb->context_type = RETRO_HW_CONTEXT_OPENGLES_VERSION; - cb->version_major = major; - cb->version_minor = minor; - } - else - { - cb->context_type = RETRO_HW_CONTEXT_OPENGLES3; - cb->version_major = 0; - cb->version_minor = 0; - } - - if (g_retro_environment_callback(RETRO_ENVIRONMENT_SET_HW_RENDER, cb)) - return true; - } - - Log_ErrorPrint("Failed to set any GL HW renderer"); - return false; -} - -std::unique_ptr OpenGLHostDisplay::Create(bool debug_device) -{ - const retro_hw_context_type context_type = g_libretro_host_interface.GetHWRenderCallback().context_type; - const GLADloadproc get_proc_address = [](const char* sym) -> void* { - return reinterpret_cast(g_libretro_host_interface.GetHWRenderCallback().get_proc_address(sym)); - }; - const bool is_gles = - (context_type == RETRO_HW_CONTEXT_OPENGLES3 || context_type == RETRO_HW_CONTEXT_OPENGLES_VERSION); - - // Load GLAD. - const auto load_result = is_gles ? gladLoadGLES2Loader(get_proc_address) : gladLoadGLLoader(get_proc_address); - if (!load_result) - { - Log_ErrorPrintf("Failed to load GL functions"); - return nullptr; - } - -#if 0 - // Disabled until we can turn it off as well - if (debug_device && GLAD_GL_KHR_debug) - { - glad_glDebugMessageCallbackKHR(GLDebugCallback, nullptr); - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - } -#endif - - std::unique_ptr display = std::make_unique(is_gles); - if (!display->CreateGLResources()) - { - Log_ErrorPrint("Failed to create GL resources"); - return nullptr; - } - - return display; -} - -bool OpenGLHostDisplay::CreateGLResources() -{ - static constexpr char fullscreen_quad_vertex_shader[] = R"( -uniform vec4 u_src_rect; -out vec2 v_tex0; - -void main() -{ - vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2)); - v_tex0 = u_src_rect.xy + pos * u_src_rect.zw; - gl_Position = vec4(pos * vec2(2.0f, -2.0f) + vec2(-1.0f, 1.0f), 0.0f, 1.0f); -} -)"; - - static constexpr char display_fragment_shader[] = R"( -uniform sampler2D samp0; - -in vec2 v_tex0; -out vec4 o_col0; - -void main() -{ - o_col0 = texture(samp0, v_tex0); -} -)"; - - if (!m_display_program.Compile(GetGLSLVersionHeader() + fullscreen_quad_vertex_shader, {}, - GetGLSLVersionHeader() + display_fragment_shader)) - { - Log_ErrorPrintf("Failed to compile display shaders"); - return false; - } - - if (!m_is_gles) - m_display_program.BindFragData(0, "o_col0"); - - if (!m_display_program.Link()) - { - Log_ErrorPrintf("Failed to link display program"); - return false; - } - - m_display_program.Bind(); - m_display_program.RegisterUniform("u_src_rect"); - m_display_program.RegisterUniform("samp0"); - m_display_program.Uniform1i(1, 0); - - glGenVertexArrays(1, &m_display_vao); - - // samplers - glGenSamplers(1, &m_display_nearest_sampler); - glSamplerParameteri(m_display_nearest_sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glSamplerParameteri(m_display_nearest_sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glGenSamplers(1, &m_display_linear_sampler); - glSamplerParameteri(m_display_linear_sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glSamplerParameteri(m_display_linear_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - return true; -} - -void OpenGLHostDisplay::Render() -{ - if (m_display_width != m_last_display_width || m_display_height != m_last_display_height) - { - retro_game_geometry geom = {}; - geom.base_width = m_display_width; - geom.base_height = m_display_height; - geom.aspect_ratio = m_display_pixel_aspect_ratio; - - if (!g_retro_environment_callback(RETRO_ENVIRONMENT_SET_GEOMETRY, &geom)) - Log_WarningPrint("RETRO_ENVIRONMENT_SET_GEOMETRY failed"); - - m_last_display_width = m_display_width; - m_last_display_height = m_display_height; - } - - const GLuint fbo = static_cast(g_libretro_host_interface.GetHWRenderCallback().get_current_framebuffer()); - - glDisable(GL_SCISSOR_TEST); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT); - - RenderDisplay(); - - g_retro_video_refresh_callback(RETRO_HW_FRAME_BUFFER_VALID, m_display_width, m_display_height, 0); - - GL::Program::ResetLastProgram(); -} - -void OpenGLHostDisplay::RenderDisplay() -{ - if (!m_display_texture_handle) - return; - - const auto [vp_left, vp_top, vp_width, vp_height] = - CalculateDrawRect(m_display_width, m_display_height, m_display_top_margin); - - glViewport(vp_left, m_display_height - vp_top - vp_height, vp_width, vp_height); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glDisable(GL_SCISSOR_TEST); - glDepthMask(GL_FALSE); - m_display_program.Bind(); - m_display_program.Uniform4f( - 0, static_cast(m_display_texture_view_x) / static_cast(m_display_texture_width), - static_cast(m_display_texture_view_y) / static_cast(m_display_texture_height), - (static_cast(m_display_texture_view_width) - 0.5f) / static_cast(m_display_texture_width), - (static_cast(m_display_texture_view_height) + 0.5f) / static_cast(m_display_texture_height)); - glBindTexture(GL_TEXTURE_2D, static_cast(reinterpret_cast(m_display_texture_handle))); - glBindSampler(0, m_display_linear_filtering ? m_display_linear_sampler : m_display_nearest_sampler); - glBindVertexArray(m_display_vao); - glDrawArrays(GL_TRIANGLES, 0, 3); - glBindSampler(0, 0); -} diff --git a/src/duckstation-libretro/opengl_host_display.h b/src/duckstation-libretro/opengl_host_display.h deleted file mode 100644 index d81da79c8..000000000 --- a/src/duckstation-libretro/opengl_host_display.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once -#include "common/gl/program.h" -#include "common/gl/texture.h" -#include "core/host_display.h" -#include "libretro.h" -#include -#include - -class OpenGLHostDisplay final : public HostDisplay -{ -public: - OpenGLHostDisplay(bool is_gles); - ~OpenGLHostDisplay(); - - static bool RequestHardwareRendererContext(retro_hw_render_callback* cb); - - static std::unique_ptr Create(bool debug_device); - - RenderAPI GetRenderAPI() const override; - void* GetRenderDevice() const override; - void* GetRenderContext() const override; - - std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, - bool dynamic) override; - void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, - u32 data_stride) override; - bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) override; - - void SetVSync(bool enabled) override; - - void Render() override; - -private: - const char* GetGLSLVersionString() const; - std::string GetGLSLVersionHeader() const; - - bool GetGLContext(bool debug_device); - bool CreateGLResources(); - - void RenderDisplay(); - - GL::Program m_display_program; - GLuint m_display_vao = 0; - GLuint m_display_nearest_sampler = 0; - GLuint m_display_linear_sampler = 0; - - s32 m_last_display_width = -1; - s32 m_last_display_height = -1; - - retro_hw_render_callback m_render_callback = {}; - bool m_is_gles = false; -};