mirror of
https://github.com/jessicanataliagta/PSPRecomp
synced 2026-09-26 16:49:34 -04:00
37e5469cbd
initial release
745 lines
32 KiB
C++
745 lines
32 KiB
C++
#include "dx12_presenter.hpp"
|
|
#include "vcs_runtime_log.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#if defined(_WIN32)
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
#include <d3d12.h>
|
|
#include <d3dcompiler.h>
|
|
#include <dxgi1_6.h>
|
|
#include <wrl/client.h>
|
|
#endif
|
|
|
|
namespace vcs {
|
|
|
|
#if defined(_WIN32)
|
|
namespace {
|
|
|
|
using Microsoft::WRL::ComPtr;
|
|
constexpr UINT kFrameCount = 2u;
|
|
constexpr DXGI_FORMAT kSwapchainFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
[[nodiscard]] std::string hr_text(HRESULT hr, const char *where) {
|
|
std::ostringstream out;
|
|
out << where << " failed (HRESULT=0x" << std::hex << std::uppercase
|
|
<< static_cast<unsigned long>(hr) << ')';
|
|
LPSTR message = nullptr;
|
|
const DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS;
|
|
if (FormatMessageA(flags, nullptr, static_cast<DWORD>(hr),
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
reinterpret_cast<LPSTR>(&message), 0u, nullptr) != 0u && message != nullptr) {
|
|
std::string text(message);
|
|
LocalFree(message);
|
|
while (!text.empty() && (text.back() == '\r' || text.back() == '\n')) text.pop_back();
|
|
if (!text.empty()) out << ": " << text;
|
|
}
|
|
return out.str();
|
|
}
|
|
|
|
struct FrameResources {
|
|
ComPtr<ID3D12CommandAllocator> allocator;
|
|
ComPtr<ID3D12Resource> source_texture;
|
|
ComPtr<ID3D12Resource> upload_buffer;
|
|
std::byte *mapped_upload{};
|
|
UINT64 upload_capacity{};
|
|
UINT64 fence_value{};
|
|
D3D12_RESOURCE_STATES source_state{D3D12_RESOURCE_STATE_COPY_DEST};
|
|
std::uint32_t source_width{};
|
|
std::uint32_t source_height{};
|
|
UINT source_row_pitch{};
|
|
|
|
void release_source() noexcept {
|
|
if (upload_buffer && mapped_upload != nullptr) upload_buffer->Unmap(0u, nullptr);
|
|
mapped_upload = nullptr;
|
|
upload_buffer.Reset();
|
|
source_texture.Reset();
|
|
upload_capacity = 0u;
|
|
source_width = 0u;
|
|
source_height = 0u;
|
|
source_row_pitch = 0u;
|
|
source_state = D3D12_RESOURCE_STATE_COPY_DEST;
|
|
}
|
|
};
|
|
|
|
struct PresenterState {
|
|
std::mutex mutex;
|
|
HWND hwnd{};
|
|
ComPtr<IDXGIFactory6> factory;
|
|
ComPtr<IDXGIAdapter1> adapter;
|
|
ComPtr<ID3D12Device> device;
|
|
ComPtr<ID3D12CommandQueue> queue;
|
|
ComPtr<IDXGISwapChain3> swapchain;
|
|
ComPtr<ID3D12DescriptorHeap> rtv_heap;
|
|
ComPtr<ID3D12DescriptorHeap> srv_heap;
|
|
ComPtr<ID3D12GraphicsCommandList> command_list;
|
|
ComPtr<ID3D12RootSignature> root_signature;
|
|
ComPtr<ID3D12PipelineState> pipeline;
|
|
ComPtr<ID3D12Fence> fence;
|
|
HANDLE fence_event{};
|
|
std::array<ComPtr<ID3D12Resource>, kFrameCount> backbuffers;
|
|
std::array<FrameResources, kFrameCount> frames;
|
|
UINT rtv_stride{};
|
|
UINT srv_stride{};
|
|
UINT backbuffer_index{};
|
|
std::uint32_t client_width{};
|
|
std::uint32_t client_height{};
|
|
UINT64 next_fence_value{1u};
|
|
bool tearing_supported{};
|
|
bool initialized{};
|
|
bool hardware_adapter{};
|
|
std::string adapter_name;
|
|
std::string last_error;
|
|
};
|
|
|
|
PresenterState &state() {
|
|
static PresenterState instance;
|
|
return instance;
|
|
}
|
|
|
|
[[nodiscard]] D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle(PresenterState &s, UINT index) noexcept {
|
|
D3D12_CPU_DESCRIPTOR_HANDLE handle = s.rtv_heap->GetCPUDescriptorHandleForHeapStart();
|
|
handle.ptr += static_cast<SIZE_T>(index) * s.rtv_stride;
|
|
return handle;
|
|
}
|
|
|
|
[[nodiscard]] D3D12_CPU_DESCRIPTOR_HANDLE srv_cpu_handle(PresenterState &s, UINT index) noexcept {
|
|
D3D12_CPU_DESCRIPTOR_HANDLE handle = s.srv_heap->GetCPUDescriptorHandleForHeapStart();
|
|
handle.ptr += static_cast<SIZE_T>(index) * s.srv_stride;
|
|
return handle;
|
|
}
|
|
|
|
[[nodiscard]] D3D12_GPU_DESCRIPTOR_HANDLE srv_gpu_handle(PresenterState &s, UINT index) noexcept {
|
|
D3D12_GPU_DESCRIPTOR_HANDLE handle = s.srv_heap->GetGPUDescriptorHandleForHeapStart();
|
|
handle.ptr += static_cast<UINT64>(index) * s.srv_stride;
|
|
return handle;
|
|
}
|
|
|
|
[[nodiscard]] bool wait_for_fence(PresenterState &s, UINT64 value, std::string &error) noexcept {
|
|
if (value == 0u || s.fence->GetCompletedValue() >= value) return true;
|
|
const HRESULT hr = s.fence->SetEventOnCompletion(value, s.fence_event);
|
|
if (FAILED(hr)) {
|
|
error = hr_text(hr, "ID3D12Fence::SetEventOnCompletion");
|
|
return false;
|
|
}
|
|
const DWORD wait = WaitForSingleObject(s.fence_event, 5000u);
|
|
if (wait != WAIT_OBJECT_0) {
|
|
std::ostringstream out;
|
|
out << "DirectX 12 fence wait failed/timed out (wait=" << wait << ')';
|
|
if (s.device) {
|
|
const HRESULT removed = s.device->GetDeviceRemovedReason();
|
|
if (FAILED(removed)) out << "; device removed reason=0x" << std::hex
|
|
<< static_cast<unsigned long>(removed);
|
|
}
|
|
error = out.str();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] bool wait_idle(PresenterState &s, std::string &error) noexcept {
|
|
if (!s.queue || !s.fence) return true;
|
|
const UINT64 value = s.next_fence_value++;
|
|
const HRESULT signal = s.queue->Signal(s.fence.Get(), value);
|
|
if (FAILED(signal)) {
|
|
error = hr_text(signal, "ID3D12CommandQueue::Signal");
|
|
return false;
|
|
}
|
|
return wait_for_fence(s, value, error);
|
|
}
|
|
|
|
void release_backbuffers(PresenterState &s) noexcept {
|
|
for (auto &buffer : s.backbuffers) buffer.Reset();
|
|
}
|
|
|
|
[[nodiscard]] bool create_backbuffers(PresenterState &s, std::string &error) noexcept {
|
|
for (UINT i = 0u; i < kFrameCount; ++i) {
|
|
HRESULT hr = s.swapchain->GetBuffer(i, IID_PPV_ARGS(&s.backbuffers[i]));
|
|
if (FAILED(hr)) {
|
|
error = hr_text(hr, "IDXGISwapChain::GetBuffer");
|
|
return false;
|
|
}
|
|
s.device->CreateRenderTargetView(s.backbuffers[i].Get(), nullptr, rtv_handle(s, i));
|
|
}
|
|
s.backbuffer_index = s.swapchain->GetCurrentBackBufferIndex();
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] bool resize_swapchain_if_needed(PresenterState &s, std::string &error) noexcept {
|
|
RECT client{};
|
|
if (!GetClientRect(s.hwnd, &client)) {
|
|
error = "GetClientRect failed for DirectX 12 window";
|
|
return false;
|
|
}
|
|
const std::uint32_t width = static_cast<std::uint32_t>(std::max<LONG>(1, client.right - client.left));
|
|
const std::uint32_t height = static_cast<std::uint32_t>(std::max<LONG>(1, client.bottom - client.top));
|
|
if (width == s.client_width && height == s.client_height && s.backbuffers[0]) return true;
|
|
if (!wait_idle(s, error)) return false;
|
|
release_backbuffers(s);
|
|
const UINT flags = s.tearing_supported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0u;
|
|
const HRESULT hr = s.swapchain->ResizeBuffers(kFrameCount, width, height,
|
|
kSwapchainFormat, flags);
|
|
if (FAILED(hr)) {
|
|
error = hr_text(hr, "IDXGISwapChain::ResizeBuffers");
|
|
return false;
|
|
}
|
|
s.client_width = width;
|
|
s.client_height = height;
|
|
return create_backbuffers(s, error);
|
|
}
|
|
|
|
[[nodiscard]] bool select_adapter(PresenterState &s, std::string &error) noexcept {
|
|
ComPtr<IDXGIAdapter1> fallback;
|
|
for (UINT index = 0u;; ++index) {
|
|
ComPtr<IDXGIAdapter1> candidate;
|
|
HRESULT hr = s.factory->EnumAdapterByGpuPreference(
|
|
index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&candidate));
|
|
if (hr == DXGI_ERROR_NOT_FOUND) break;
|
|
if (FAILED(hr)) continue;
|
|
DXGI_ADAPTER_DESC1 desc{};
|
|
candidate->GetDesc1(&desc);
|
|
if ((desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) != 0u) continue;
|
|
if (SUCCEEDED(D3D12CreateDevice(candidate.Get(), D3D_FEATURE_LEVEL_11_0,
|
|
__uuidof(ID3D12Device), nullptr))) {
|
|
s.adapter = candidate;
|
|
s.hardware_adapter = true;
|
|
break;
|
|
}
|
|
if (!fallback) fallback = candidate;
|
|
}
|
|
if (!s.adapter && fallback) s.adapter = fallback;
|
|
if (!s.adapter) {
|
|
error = "No Direct3D 12-capable hardware adapter was found";
|
|
return false;
|
|
}
|
|
DXGI_ADAPTER_DESC1 desc{};
|
|
s.adapter->GetDesc1(&desc);
|
|
char utf8[512]{};
|
|
const int count = WideCharToMultiByte(CP_UTF8, 0, desc.Description, -1,
|
|
utf8, static_cast<int>(sizeof(utf8)), nullptr, nullptr);
|
|
s.adapter_name = count > 0 ? utf8 : "Direct3D 12 adapter";
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] bool create_pipeline(PresenterState &s, std::string &error) noexcept {
|
|
const bool linear = vcs_configuration().display.upscale_filter == DisplayUpscaleFilter::Bilinear;
|
|
const char *shader = R"HLSL(
|
|
Texture2D<float4> SourceTexture : register(t0);
|
|
SamplerState SourceSampler : register(s0);
|
|
struct VsOut { float4 position : SV_POSITION; float2 uv : TEXCOORD0; };
|
|
VsOut VSMain(uint id : SV_VertexID) {
|
|
VsOut o;
|
|
if (id == 0) { o.position=float4(-1.0,-1.0,0.0,1.0); o.uv=float2(0.0,1.0); }
|
|
else if (id == 1) { o.position=float4(-1.0,3.0,0.0,1.0); o.uv=float2(0.0,-1.0); }
|
|
else { o.position=float4(3.0,-1.0,0.0,1.0); o.uv=float2(2.0,1.0); }
|
|
return o;
|
|
}
|
|
float4 PSMain(VsOut input) : SV_TARGET { return SourceTexture.Sample(SourceSampler, input.uv); }
|
|
)HLSL";
|
|
UINT compile_flags = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_WARNINGS_ARE_ERRORS;
|
|
ComPtr<ID3DBlob> vs, ps, errors;
|
|
HRESULT hr = D3DCompile(shader, std::strlen(shader), "VCSNativeDx12Presenter", nullptr, nullptr,
|
|
"VSMain", "vs_5_1", compile_flags, 0u, &vs, &errors);
|
|
if (FAILED(hr)) {
|
|
error = errors ? std::string(static_cast<const char *>(errors->GetBufferPointer()), errors->GetBufferSize())
|
|
: hr_text(hr, "D3DCompile(VSMain)");
|
|
return false;
|
|
}
|
|
errors.Reset();
|
|
hr = D3DCompile(shader, std::strlen(shader), "VCSNativeDx12Presenter", nullptr, nullptr,
|
|
"PSMain", "ps_5_1", compile_flags, 0u, &ps, &errors);
|
|
if (FAILED(hr)) {
|
|
error = errors ? std::string(static_cast<const char *>(errors->GetBufferPointer()), errors->GetBufferSize())
|
|
: hr_text(hr, "D3DCompile(PSMain)");
|
|
return false;
|
|
}
|
|
|
|
D3D12_DESCRIPTOR_RANGE range{};
|
|
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
|
range.NumDescriptors = 1u;
|
|
range.BaseShaderRegister = 0u;
|
|
range.RegisterSpace = 0u;
|
|
range.OffsetInDescriptorsFromTableStart = 0u;
|
|
D3D12_ROOT_PARAMETER parameter{};
|
|
parameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
parameter.DescriptorTable.NumDescriptorRanges = 1u;
|
|
parameter.DescriptorTable.pDescriptorRanges = ⦥
|
|
parameter.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
|
D3D12_STATIC_SAMPLER_DESC sampler{};
|
|
sampler.Filter = linear ? D3D12_FILTER_MIN_MAG_MIP_LINEAR : D3D12_FILTER_MIN_MAG_MIP_POINT;
|
|
sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
sampler.MipLODBias = 0.0f;
|
|
sampler.MaxAnisotropy = 1u;
|
|
sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
|
|
sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
|
|
sampler.MinLOD = 0.0f;
|
|
sampler.MaxLOD = D3D12_FLOAT32_MAX;
|
|
sampler.ShaderRegister = 0u;
|
|
sampler.RegisterSpace = 0u;
|
|
sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
|
D3D12_ROOT_SIGNATURE_DESC root{};
|
|
root.NumParameters = 1u;
|
|
root.pParameters = ¶meter;
|
|
root.NumStaticSamplers = 1u;
|
|
root.pStaticSamplers = &sampler;
|
|
root.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
|
|
ComPtr<ID3DBlob> root_blob;
|
|
errors.Reset();
|
|
hr = D3D12SerializeRootSignature(&root, D3D_ROOT_SIGNATURE_VERSION_1,
|
|
&root_blob, &errors);
|
|
if (FAILED(hr)) {
|
|
error = errors ? std::string(static_cast<const char *>(errors->GetBufferPointer()), errors->GetBufferSize())
|
|
: hr_text(hr, "D3D12SerializeRootSignature");
|
|
return false;
|
|
}
|
|
hr = s.device->CreateRootSignature(0u, root_blob->GetBufferPointer(), root_blob->GetBufferSize(),
|
|
IID_PPV_ARGS(&s.root_signature));
|
|
if (FAILED(hr)) {
|
|
error = hr_text(hr, "ID3D12Device::CreateRootSignature");
|
|
return false;
|
|
}
|
|
|
|
D3D12_GRAPHICS_PIPELINE_STATE_DESC pso{};
|
|
pso.pRootSignature = s.root_signature.Get();
|
|
pso.VS = {vs->GetBufferPointer(), vs->GetBufferSize()};
|
|
pso.PS = {ps->GetBufferPointer(), ps->GetBufferSize()};
|
|
pso.BlendState.AlphaToCoverageEnable = FALSE;
|
|
pso.BlendState.IndependentBlendEnable = FALSE;
|
|
D3D12_RENDER_TARGET_BLEND_DESC rt{};
|
|
rt.BlendEnable = FALSE;
|
|
rt.LogicOpEnable = FALSE;
|
|
rt.SrcBlend = D3D12_BLEND_ONE;
|
|
rt.DestBlend = D3D12_BLEND_ZERO;
|
|
rt.BlendOp = D3D12_BLEND_OP_ADD;
|
|
rt.SrcBlendAlpha = D3D12_BLEND_ONE;
|
|
rt.DestBlendAlpha = D3D12_BLEND_ZERO;
|
|
rt.BlendOpAlpha = D3D12_BLEND_OP_ADD;
|
|
rt.LogicOp = D3D12_LOGIC_OP_NOOP;
|
|
rt.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
|
|
pso.BlendState.RenderTarget[0] = rt;
|
|
pso.SampleMask = UINT_MAX;
|
|
pso.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
|
|
pso.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
|
|
pso.RasterizerState.FrontCounterClockwise = FALSE;
|
|
pso.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
|
|
pso.RasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
|
|
pso.RasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
|
|
pso.RasterizerState.DepthClipEnable = TRUE;
|
|
pso.RasterizerState.MultisampleEnable = FALSE;
|
|
pso.RasterizerState.AntialiasedLineEnable = FALSE;
|
|
pso.RasterizerState.ForcedSampleCount = 0u;
|
|
pso.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
|
|
pso.DepthStencilState.DepthEnable = FALSE;
|
|
pso.DepthStencilState.StencilEnable = FALSE;
|
|
pso.InputLayout = {nullptr, 0u};
|
|
pso.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
|
|
pso.NumRenderTargets = 1u;
|
|
pso.RTVFormats[0] = kSwapchainFormat;
|
|
pso.SampleDesc.Count = 1u;
|
|
hr = s.device->CreateGraphicsPipelineState(&pso, IID_PPV_ARGS(&s.pipeline));
|
|
if (FAILED(hr)) {
|
|
error = hr_text(hr, "ID3D12Device::CreateGraphicsPipelineState");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] bool ensure_source(PresenterState &s, UINT frame_index,
|
|
std::uint32_t width, std::uint32_t height,
|
|
std::string &error) noexcept {
|
|
FrameResources &frame = s.frames[frame_index];
|
|
if (frame.source_texture && frame.source_width == width && frame.source_height == height)
|
|
return true;
|
|
if (!wait_for_fence(s, frame.fence_value, error)) return false;
|
|
frame.release_source();
|
|
|
|
D3D12_RESOURCE_DESC texture{};
|
|
texture.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
texture.Width = width;
|
|
texture.Height = height;
|
|
texture.DepthOrArraySize = 1u;
|
|
texture.MipLevels = 1u;
|
|
texture.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
texture.SampleDesc.Count = 1u;
|
|
texture.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
|
texture.Flags = D3D12_RESOURCE_FLAG_NONE;
|
|
D3D12_HEAP_PROPERTIES default_heap{};
|
|
default_heap.Type = D3D12_HEAP_TYPE_DEFAULT;
|
|
HRESULT hr = s.device->CreateCommittedResource(&default_heap, D3D12_HEAP_FLAG_NONE,
|
|
&texture, D3D12_RESOURCE_STATE_COPY_DEST,
|
|
nullptr, IID_PPV_ARGS(&frame.source_texture));
|
|
if (FAILED(hr)) {
|
|
error = hr_text(hr, "CreateCommittedResource(DX12 source texture)");
|
|
return false;
|
|
}
|
|
|
|
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{};
|
|
UINT rows = 0u;
|
|
UINT64 row_size = 0u, total = 0u;
|
|
s.device->GetCopyableFootprints(&texture, 0u, 1u, 0u, &footprint, &rows, &row_size, &total);
|
|
D3D12_RESOURCE_DESC buffer{};
|
|
buffer.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
|
buffer.Width = std::max<UINT64>(total, 256u);
|
|
buffer.Height = 1u;
|
|
buffer.DepthOrArraySize = 1u;
|
|
buffer.MipLevels = 1u;
|
|
buffer.Format = DXGI_FORMAT_UNKNOWN;
|
|
buffer.SampleDesc.Count = 1u;
|
|
buffer.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
|
D3D12_HEAP_PROPERTIES upload_heap{};
|
|
upload_heap.Type = D3D12_HEAP_TYPE_UPLOAD;
|
|
hr = s.device->CreateCommittedResource(&upload_heap, D3D12_HEAP_FLAG_NONE,
|
|
&buffer, D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
nullptr, IID_PPV_ARGS(&frame.upload_buffer));
|
|
if (FAILED(hr)) {
|
|
error = hr_text(hr, "CreateCommittedResource(DX12 upload buffer)");
|
|
frame.release_source();
|
|
return false;
|
|
}
|
|
void *mapped = nullptr;
|
|
const D3D12_RANGE no_read{0u, 0u};
|
|
hr = frame.upload_buffer->Map(0u, &no_read, &mapped);
|
|
if (FAILED(hr) || mapped == nullptr) {
|
|
error = hr_text(hr, "ID3D12Resource::Map(DX12 upload buffer)");
|
|
frame.release_source();
|
|
return false;
|
|
}
|
|
frame.mapped_upload = static_cast<std::byte *>(mapped);
|
|
frame.upload_capacity = total;
|
|
frame.source_width = width;
|
|
frame.source_height = height;
|
|
frame.source_row_pitch = footprint.Footprint.RowPitch;
|
|
frame.source_state = D3D12_RESOURCE_STATE_COPY_DEST;
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC srv{};
|
|
srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
srv.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
srv.Texture2D.MipLevels = 1u;
|
|
s.device->CreateShaderResourceView(frame.source_texture.Get(), &srv,
|
|
srv_cpu_handle(s, frame_index));
|
|
return true;
|
|
}
|
|
|
|
void barrier(ID3D12GraphicsCommandList *list, ID3D12Resource *resource,
|
|
D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) noexcept {
|
|
if (before == after) return;
|
|
D3D12_RESOURCE_BARRIER b{};
|
|
b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
b.Transition.pResource = resource;
|
|
b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
|
b.Transition.StateBefore = before;
|
|
b.Transition.StateAfter = after;
|
|
list->ResourceBarrier(1u, &b);
|
|
}
|
|
|
|
void shutdown_unlocked(PresenterState &s) noexcept {
|
|
std::string ignored;
|
|
if (s.initialized || s.queue) (void)wait_idle(s, ignored);
|
|
for (FrameResources &frame : s.frames) {
|
|
frame.release_source();
|
|
frame.allocator.Reset();
|
|
frame.fence_value = 0u;
|
|
}
|
|
release_backbuffers(s);
|
|
if (s.fence_event != nullptr) CloseHandle(s.fence_event);
|
|
s.fence_event = nullptr;
|
|
s.fence.Reset();
|
|
s.pipeline.Reset();
|
|
s.root_signature.Reset();
|
|
s.command_list.Reset();
|
|
s.srv_heap.Reset();
|
|
s.rtv_heap.Reset();
|
|
s.swapchain.Reset();
|
|
s.queue.Reset();
|
|
s.device.Reset();
|
|
s.adapter.Reset();
|
|
s.factory.Reset();
|
|
s.hwnd = nullptr;
|
|
s.client_width = s.client_height = 0u;
|
|
s.next_fence_value = 1u;
|
|
s.tearing_supported = false;
|
|
s.initialized = false;
|
|
s.hardware_adapter = false;
|
|
s.adapter_name.clear();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool dx12_presenter_initialize(void *native_window, std::string &error) noexcept {
|
|
PresenterState &s = state();
|
|
std::lock_guard<std::mutex> guard(s.mutex);
|
|
if (s.initialized && s.hwnd == static_cast<HWND>(native_window)) return true;
|
|
if (native_window == nullptr) {
|
|
error = "DirectX 12 presenter received a null HWND";
|
|
return false;
|
|
}
|
|
shutdown_unlocked(s);
|
|
s.hwnd = static_cast<HWND>(native_window);
|
|
|
|
UINT factory_flags = 0u;
|
|
if (const char *debug = std::getenv("PSPRECOMP_DX12_DEBUG");
|
|
debug != nullptr && *debug != '\0' && *debug != '0') {
|
|
ComPtr<ID3D12Debug> debug_layer;
|
|
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_layer)))) {
|
|
debug_layer->EnableDebugLayer();
|
|
factory_flags |= DXGI_CREATE_FACTORY_DEBUG;
|
|
}
|
|
}
|
|
HRESULT hr = CreateDXGIFactory2(factory_flags, IID_PPV_ARGS(&s.factory));
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CreateDXGIFactory2"); return false; }
|
|
if (!select_adapter(s, error)) { s.last_error = error; return false; }
|
|
hr = D3D12CreateDevice(s.adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&s.device));
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "D3D12CreateDevice"); return false; }
|
|
|
|
D3D12_COMMAND_QUEUE_DESC queue_desc{};
|
|
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
|
queue_desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
|
|
hr = s.device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&s.queue));
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CreateCommandQueue"); return false; }
|
|
|
|
BOOL tearing = FALSE;
|
|
ComPtr<IDXGIFactory5> factory5;
|
|
if (SUCCEEDED(s.factory.As(&factory5))) {
|
|
if (FAILED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING,
|
|
&tearing, sizeof(tearing)))) tearing = FALSE;
|
|
}
|
|
s.tearing_supported = tearing == TRUE;
|
|
|
|
RECT client{};
|
|
GetClientRect(s.hwnd, &client);
|
|
s.client_width = static_cast<std::uint32_t>(std::max<LONG>(1, client.right - client.left));
|
|
s.client_height = static_cast<std::uint32_t>(std::max<LONG>(1, client.bottom - client.top));
|
|
DXGI_SWAP_CHAIN_DESC1 swap_desc{};
|
|
swap_desc.Width = s.client_width;
|
|
swap_desc.Height = s.client_height;
|
|
swap_desc.Format = kSwapchainFormat;
|
|
swap_desc.SampleDesc.Count = 1u;
|
|
swap_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
swap_desc.BufferCount = kFrameCount;
|
|
swap_desc.Scaling = DXGI_SCALING_STRETCH;
|
|
swap_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
|
swap_desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
|
|
swap_desc.Flags = s.tearing_supported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0u;
|
|
ComPtr<IDXGISwapChain1> swap1;
|
|
hr = s.factory->CreateSwapChainForHwnd(s.queue.Get(), s.hwnd, &swap_desc,
|
|
nullptr, nullptr, &swap1);
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CreateSwapChainForHwnd"); return false; }
|
|
(void)s.factory->MakeWindowAssociation(s.hwnd, DXGI_MWA_NO_ALT_ENTER);
|
|
hr = swap1.As(&s.swapchain);
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "Query IDXGISwapChain3"); return false; }
|
|
|
|
D3D12_DESCRIPTOR_HEAP_DESC rtv_desc{};
|
|
rtv_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
|
|
rtv_desc.NumDescriptors = kFrameCount;
|
|
hr = s.device->CreateDescriptorHeap(&rtv_desc, IID_PPV_ARGS(&s.rtv_heap));
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CreateDescriptorHeap(RTV)"); return false; }
|
|
s.rtv_stride = s.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
|
|
|
|
D3D12_DESCRIPTOR_HEAP_DESC srv_desc{};
|
|
srv_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
|
srv_desc.NumDescriptors = kFrameCount;
|
|
srv_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
|
hr = s.device->CreateDescriptorHeap(&srv_desc, IID_PPV_ARGS(&s.srv_heap));
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CreateDescriptorHeap(SRV)"); return false; }
|
|
s.srv_stride = s.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
|
|
|
for (FrameResources &frame : s.frames) {
|
|
hr = s.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
IID_PPV_ARGS(&frame.allocator));
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CreateCommandAllocator"); return false; }
|
|
}
|
|
hr = s.device->CreateCommandList(0u, D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
s.frames[0].allocator.Get(), nullptr,
|
|
IID_PPV_ARGS(&s.command_list));
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CreateCommandList"); return false; }
|
|
s.command_list->Close();
|
|
hr = s.device->CreateFence(0u, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&s.fence));
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CreateFence"); return false; }
|
|
s.fence_event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
|
if (s.fence_event == nullptr) { error = s.last_error = "CreateEventW failed for DirectX 12 fence"; return false; }
|
|
if (!create_backbuffers(s, error)) { s.last_error = error; return false; }
|
|
if (!create_pipeline(s, error)) { s.last_error = error; return false; }
|
|
s.initialized = true;
|
|
s.last_error.clear();
|
|
std::ostringstream log;
|
|
log << "dx12 presenter initialized adapter=" << s.adapter_name
|
|
<< " frames_in_flight=" << kFrameCount
|
|
<< (s.tearing_supported ? " tearing=1" : " tearing=0")
|
|
<< " size=" << s.client_width << 'x' << s.client_height;
|
|
runtime_log_line(log.str());
|
|
return true;
|
|
}
|
|
|
|
bool dx12_presenter_present_rgba(std::span<const std::byte> rgba,
|
|
std::uint32_t source_width,
|
|
std::uint32_t source_height,
|
|
const DisplayConfiguration &display,
|
|
bool force_preserve_aspect,
|
|
std::string &error) noexcept {
|
|
PresenterState &s = state();
|
|
std::lock_guard<std::mutex> guard(s.mutex);
|
|
if (!s.initialized || !s.swapchain || source_width == 0u || source_height == 0u) {
|
|
error = "DirectX 12 presenter is not initialized";
|
|
return false;
|
|
}
|
|
const std::size_t required = static_cast<std::size_t>(source_width) * source_height * 4u;
|
|
if (rgba.size() < required) { error = "DirectX 12 presenter received a short RGBA frame"; return false; }
|
|
if (!resize_swapchain_if_needed(s, error)) { s.last_error = error; return false; }
|
|
|
|
const UINT frame_index = s.swapchain->GetCurrentBackBufferIndex();
|
|
FrameResources &frame = s.frames[frame_index];
|
|
if (!wait_for_fence(s, frame.fence_value, error)) { s.last_error = error; return false; }
|
|
if (!ensure_source(s, frame_index, source_width, source_height, error)) {
|
|
s.last_error = error; return false;
|
|
}
|
|
|
|
const std::size_t source_row = static_cast<std::size_t>(source_width) * 4u;
|
|
for (std::uint32_t y = 0u; y < source_height; ++y) {
|
|
std::memcpy(frame.mapped_upload + static_cast<std::size_t>(y) * frame.source_row_pitch,
|
|
rgba.data() + static_cast<std::size_t>(y) * source_row, source_row);
|
|
}
|
|
const D3D12_RANGE written{0u, static_cast<SIZE_T>(frame.source_row_pitch) * source_height};
|
|
(void)written; // Persistently mapped UPLOAD heaps are coherent for CPU writes.
|
|
|
|
HRESULT hr = frame.allocator->Reset();
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "CommandAllocator::Reset"); return false; }
|
|
hr = s.command_list->Reset(frame.allocator.Get(), s.pipeline.Get());
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "GraphicsCommandList::Reset"); return false; }
|
|
|
|
if (frame.source_state != D3D12_RESOURCE_STATE_COPY_DEST) {
|
|
barrier(s.command_list.Get(), frame.source_texture.Get(), frame.source_state,
|
|
D3D12_RESOURCE_STATE_COPY_DEST);
|
|
frame.source_state = D3D12_RESOURCE_STATE_COPY_DEST;
|
|
}
|
|
D3D12_RESOURCE_DESC texture_desc = frame.source_texture->GetDesc();
|
|
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{};
|
|
UINT rows = 0u;
|
|
UINT64 row_size = 0u, total = 0u;
|
|
s.device->GetCopyableFootprints(&texture_desc, 0u, 1u, 0u,
|
|
&footprint, &rows, &row_size, &total);
|
|
D3D12_TEXTURE_COPY_LOCATION dst{};
|
|
dst.pResource = frame.source_texture.Get();
|
|
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
|
dst.SubresourceIndex = 0u;
|
|
D3D12_TEXTURE_COPY_LOCATION src{};
|
|
src.pResource = frame.upload_buffer.Get();
|
|
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
|
src.PlacedFootprint = footprint;
|
|
s.command_list->CopyTextureRegion(&dst, 0u, 0u, 0u, &src, nullptr);
|
|
barrier(s.command_list.Get(), frame.source_texture.Get(), D3D12_RESOURCE_STATE_COPY_DEST,
|
|
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
|
frame.source_state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
|
|
|
|
ID3D12Resource *backbuffer = s.backbuffers[frame_index].Get();
|
|
barrier(s.command_list.Get(), backbuffer, D3D12_RESOURCE_STATE_PRESENT,
|
|
D3D12_RESOURCE_STATE_RENDER_TARGET);
|
|
const D3D12_CPU_DESCRIPTOR_HANDLE rtv = rtv_handle(s, frame_index);
|
|
s.command_list->OMSetRenderTargets(1u, &rtv, FALSE, nullptr);
|
|
constexpr float black[4]{0.0f, 0.0f, 0.0f, 1.0f};
|
|
s.command_list->ClearRenderTargetView(rtv, black, 0u, nullptr);
|
|
|
|
const DisplayAspectMode mode = force_preserve_aspect ? DisplayAspectMode::Preserve
|
|
: display.aspect_mode;
|
|
const PresentationRectangle rect = calculate_presentation_rectangle(
|
|
s.client_width, s.client_height, source_width, source_height,
|
|
mode, display.integer_scale);
|
|
D3D12_VIEWPORT viewport{};
|
|
viewport.TopLeftX = static_cast<float>(rect.x);
|
|
viewport.TopLeftY = static_cast<float>(rect.y);
|
|
viewport.Width = static_cast<float>(std::max(1, rect.width));
|
|
viewport.Height = static_cast<float>(std::max(1, rect.height));
|
|
viewport.MinDepth = 0.0f;
|
|
viewport.MaxDepth = 1.0f;
|
|
D3D12_RECT scissor{rect.x, rect.y, rect.x + std::max(1, rect.width),
|
|
rect.y + std::max(1, rect.height)};
|
|
s.command_list->RSSetViewports(1u, &viewport);
|
|
s.command_list->RSSetScissorRects(1u, &scissor);
|
|
ID3D12DescriptorHeap *heaps[]{s.srv_heap.Get()};
|
|
s.command_list->SetDescriptorHeaps(1u, heaps);
|
|
s.command_list->SetGraphicsRootSignature(s.root_signature.Get());
|
|
s.command_list->SetGraphicsRootDescriptorTable(0u, srv_gpu_handle(s, frame_index));
|
|
s.command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
s.command_list->DrawInstanced(3u, 1u, 0u, 0u);
|
|
barrier(s.command_list.Get(), backbuffer, D3D12_RESOURCE_STATE_RENDER_TARGET,
|
|
D3D12_RESOURCE_STATE_PRESENT);
|
|
hr = s.command_list->Close();
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "GraphicsCommandList::Close"); return false; }
|
|
ID3D12CommandList *lists[]{s.command_list.Get()};
|
|
s.queue->ExecuteCommandLists(1u, lists);
|
|
|
|
const UINT present_flags = s.tearing_supported ? DXGI_PRESENT_ALLOW_TEARING : 0u;
|
|
hr = s.swapchain->Present(0u, present_flags);
|
|
if (FAILED(hr)) {
|
|
std::ostringstream out;
|
|
out << hr_text(hr, "IDXGISwapChain::Present");
|
|
const HRESULT removed = s.device->GetDeviceRemovedReason();
|
|
if (FAILED(removed)) out << "; device removed reason=0x" << std::hex
|
|
<< static_cast<unsigned long>(removed);
|
|
error = s.last_error = out.str();
|
|
runtime_log_error("dx12 present", error);
|
|
return false;
|
|
}
|
|
frame.fence_value = s.next_fence_value++;
|
|
hr = s.queue->Signal(s.fence.Get(), frame.fence_value);
|
|
if (FAILED(hr)) { error = s.last_error = hr_text(hr, "ID3D12CommandQueue::Signal"); return false; }
|
|
s.backbuffer_index = s.swapchain->GetCurrentBackBufferIndex();
|
|
s.last_error.clear();
|
|
return true;
|
|
}
|
|
|
|
void dx12_presenter_shutdown() noexcept {
|
|
PresenterState &s = state();
|
|
std::lock_guard<std::mutex> guard(s.mutex);
|
|
if (s.initialized) runtime_log_line("dx12 presenter shutdown");
|
|
shutdown_unlocked(s);
|
|
}
|
|
|
|
bool dx12_presenter_active() noexcept {
|
|
PresenterState &s = state();
|
|
std::lock_guard<std::mutex> guard(s.mutex);
|
|
return s.initialized;
|
|
}
|
|
|
|
Dx12PresenterStatus dx12_presenter_status() {
|
|
PresenterState &s = state();
|
|
std::lock_guard<std::mutex> guard(s.mutex);
|
|
return {s.initialized, s.hardware_adapter, s.tearing_supported, kFrameCount,
|
|
s.adapter_name, s.last_error};
|
|
}
|
|
|
|
#else
|
|
|
|
bool dx12_presenter_initialize(void *, std::string &error) noexcept {
|
|
error = "DirectX 12 is available only on Windows";
|
|
return false;
|
|
}
|
|
bool dx12_presenter_present_rgba(std::span<const std::byte>, std::uint32_t, std::uint32_t,
|
|
const DisplayConfiguration &, bool, std::string &error) noexcept {
|
|
error = "DirectX 12 is available only on Windows";
|
|
return false;
|
|
}
|
|
void dx12_presenter_shutdown() noexcept {}
|
|
bool dx12_presenter_active() noexcept { return false; }
|
|
Dx12PresenterStatus dx12_presenter_status() { return {}; }
|
|
|
|
#endif
|
|
|
|
} // namespace vcs
|