diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj
index 22fe45004..19f685eb4 100644
--- a/src/core/core.vcxproj
+++ b/src/core/core.vcxproj
@@ -37,6 +37,8 @@
+
+
@@ -161,6 +163,8 @@
+
+
@@ -197,6 +201,8 @@
true
+
+
diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters
index 3109cb210..d4e9fd66a 100644
--- a/src/core/core.vcxproj.filters
+++ b/src/core/core.vcxproj.filters
@@ -188,6 +188,12 @@
gpu
+
+ gpu
+
+
+ gpu
+
@@ -395,6 +401,18 @@
gpu
+
+ gpu
+
+
+ gpu
+
+
+ gpu
+
+
+ gpu
+
diff --git a/src/core/gpu/d3d11_device.h b/src/core/gpu/d3d11_device.h
index 94098f57d..c6bb1b8aa 100644
--- a/src/core/gpu/d3d11_device.h
+++ b/src/core/gpu/d3d11_device.h
@@ -8,15 +8,20 @@
#include "d3d11/stream_buffer.h"
#include "d3d11_texture.h"
#include "gpu_device.h"
+#include "gpu_pipeline.h"
#include "postprocessing_chain.h"
#include
#include
#include
#include
#include
+#include
#include
#include
+class D3D11Pipeline;
+class D3D11Shader;
+
class D3D11Device final : public GPUDevice
{
public:
@@ -65,6 +70,11 @@ public:
void ResolveTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src,
u32 src_x, u32 src_y, u32 src_layer, u32 src_level, u32 width, u32 height) override;
+ std::unique_ptr CreateShaderFromBinary(GPUShader::Stage stage, gsl::span data) override;
+ std::unique_ptr CreateShaderFromSource(GPUShader::Stage stage, const std::string_view& source,
+ std::vector* out_binary = nullptr) override;
+ std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config) override;
+
bool GetHostRefreshRate(float* refresh_rate) override;
bool SetGPUTimingEnabled(bool enabled) override;
@@ -78,7 +88,12 @@ public:
static AdapterAndModeList StaticGetAdapterAndModeList();
-protected:
+private:
+ using RasterizationStateMap = std::unordered_map>;
+ using DepthStateMap = std::unordered_map>;
+ using BlendStateMap = std::unordered_map>;
+ using InputLayoutMap = std::unordered_map, GPUPipeline::InputLayoutHash>;
+
static constexpr u32 DISPLAY_UNIFORM_BUFFER_SIZE = 64;
static constexpr u32 IMGUI_VERTEX_BUFFER_SIZE = 4 * 1024 * 1024;
static constexpr u32 IMGUI_INDEX_BUFFER_SIZE = 2 * 1024 * 1024;
@@ -98,6 +113,11 @@ protected:
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
bool CreateSwapChainRTV();
+ ComPtr GetRasterizationState(const GPUPipeline::RasterizationState& rs);
+ ComPtr GetDepthState(const GPUPipeline::DepthState& ds);
+ ComPtr GetBlendState(const GPUPipeline::BlendState& bs);
+ ComPtr GetInputLayout(const GPUPipeline::InputLayout& il, const D3D11Shader* vs);
+
void RenderDisplay();
void RenderSoftwareCursor();
void RenderImGui();
@@ -142,6 +162,11 @@ protected:
ComPtr m_linear_sampler;
ComPtr m_border_sampler;
+ RasterizationStateMap m_rasterization_states;
+ DepthStateMap m_depth_states;
+ BlendStateMap m_blend_states;
+ InputLayoutMap m_input_layouts;
+
D3D11::StreamBuffer m_display_uniform_buffer;
ComPtr m_readback_staging_texture;
DXGI_FORMAT m_readback_staging_texture_format = DXGI_FORMAT_UNKNOWN;
diff --git a/src/core/gpu/d3d11_pipeline.cpp b/src/core/gpu/d3d11_pipeline.cpp
new file mode 100644
index 000000000..dade73170
--- /dev/null
+++ b/src/core/gpu/d3d11_pipeline.cpp
@@ -0,0 +1,242 @@
+// SPDX-FileCopyrightText: 2023 Connor McLaughlin
+// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
+
+#include "d3d11_pipeline.h"
+#include "d3d11_device.h"
+#include "d3d11_shader.h"
+
+#include "common/assert.h"
+#include "common/log.h"
+
+#include
+#include
+
+Log_SetChannel(D3D11Device);
+
+D3D11Pipeline::D3D11Pipeline(ComPtr rs, ComPtr ds,
+ ComPtr bs, ComPtr il, ComPtr vs,
+ ComPtr ps, D3D11_PRIMITIVE_TOPOLOGY topology)
+ : m_rs(std::move(rs)), m_ds(std::move(ds)), m_bs(std::move(bs)), m_il(std::move(il)), m_vs(std::move(vs)),
+ m_ps(std::move(ps)), m_topology(topology)
+{
+}
+
+D3D11Pipeline::~D3D11Pipeline() = default;
+
+void D3D11Pipeline::SetDebugName(const std::string_view& name)
+{
+ UnreachableCode();
+}
+
+void D3D11Pipeline::Bind(ID3D11DeviceContext* context)
+{
+ context->IASetInputLayout(GetInputLayout());
+ context->IASetPrimitiveTopology(GetPrimitiveTopology());
+ context->RSSetState(GetRasterizerState());
+ context->OMSetDepthStencilState(GetDepthStencilState(), 0);
+ context->OMSetBlendState(GetBlendState(), nullptr, 0xFFFFFFFFu);
+ context->VSSetShader(GetVertexShader(), nullptr, 0);
+ context->PSSetShader(GetPixelShader(), nullptr, 0);
+}
+
+D3D11Device::ComPtr D3D11Device::GetRasterizationState(const GPUPipeline::RasterizationState& rs)
+{
+ ComPtr drs;
+
+ const auto it = m_rasterization_states.find(rs.key);
+ if (it != m_rasterization_states.end())
+ {
+ drs = it->second;
+ return drs;
+ }
+
+ static constexpr std::array(GPUPipeline::CullMode::MaxCount)> cull_mapping = {{
+ D3D11_CULL_NONE, // None
+ D3D11_CULL_FRONT, // Front
+ D3D11_CULL_BACK, // Back
+ }};
+
+ D3D11_RASTERIZER_DESC desc = {};
+ desc.FillMode = D3D11_FILL_SOLID;
+ desc.CullMode = cull_mapping[static_cast(rs.cull_mode.GetValue())];
+ desc.ScissorEnable = TRUE;
+ // desc.MultisampleEnable ???
+
+ HRESULT hr = m_device->CreateRasterizerState(&desc, drs.GetAddressOf());
+ if (FAILED(hr))
+ Log_ErrorPrintf("Failed to create depth state with %08X", hr);
+
+ m_rasterization_states.emplace(rs.key, drs);
+ return drs;
+}
+
+D3D11Device::ComPtr D3D11Device::GetDepthState(const GPUPipeline::DepthState& ds)
+{
+ ComPtr dds;
+
+ const auto it = m_depth_states.find(ds.key);
+ if (it != m_depth_states.end())
+ {
+ dds = it->second;
+ return dds;
+ }
+
+ static constexpr std::array(GPUPipeline::DepthFunc::MaxCount)> func_mapping =
+ {{
+ D3D11_COMPARISON_NEVER, // Never
+ D3D11_COMPARISON_ALWAYS, // Always
+ D3D11_COMPARISON_LESS, // Less
+ D3D11_COMPARISON_LESS_EQUAL, // LessEqual
+ D3D11_COMPARISON_GREATER, // Greater
+ D3D11_COMPARISON_GREATER_EQUAL, // GreaterEqual
+ D3D11_COMPARISON_EQUAL, // Equal
+ }};
+
+ D3D11_DEPTH_STENCIL_DESC desc = {};
+ desc.DepthEnable = ds.depth_test != GPUPipeline::DepthFunc::Never;
+ desc.DepthFunc = func_mapping[static_cast(ds.depth_test.GetValue())];
+ desc.DepthWriteMask = ds.depth_write ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
+
+ HRESULT hr = m_device->CreateDepthStencilState(&desc, dds.GetAddressOf());
+ if (FAILED(hr))
+ Log_ErrorPrintf("Failed to create depth state with %08X", hr);
+
+ m_depth_states.emplace(ds.key, dds);
+ return dds;
+}
+
+D3D11Device::ComPtr D3D11Device::GetBlendState(const GPUPipeline::BlendState& bs)
+{
+ ComPtr dbs;
+
+ const auto it = m_blend_states.find(bs.key);
+ if (it != m_blend_states.end())
+ {
+ dbs = it->second;
+ return dbs;
+ }
+
+ static constexpr std::array(GPUPipeline::BlendFunc::MaxCount)> blend_mapping = {{
+ D3D11_BLEND_ZERO, // Zero
+ D3D11_BLEND_ONE, // One
+ D3D11_BLEND_SRC_COLOR, // SrcColor
+ D3D11_BLEND_INV_SRC_COLOR, // InvSrcColor
+ D3D11_BLEND_DEST_COLOR, // DstColor
+ D3D11_BLEND_INV_DEST_COLOR, // InvDstColor
+ D3D11_BLEND_SRC_ALPHA, // SrcAlpha
+ D3D11_BLEND_INV_SRC_ALPHA, // InvSrcAlpha
+ D3D11_BLEND_SRC1_ALPHA, // SrcAlpha1
+ D3D11_BLEND_INV_SRC1_ALPHA, // InvSrcAlpha1
+ D3D11_BLEND_DEST_ALPHA, // DstAlpha
+ D3D11_BLEND_INV_DEST_ALPHA, // InvDstAlpha
+ }};
+
+ static constexpr std::array(GPUPipeline::BlendOp::MaxCount)> op_mapping = {{
+ D3D11_BLEND_OP_ADD, // Add
+ D3D11_BLEND_OP_SUBTRACT, // Subtract
+ D3D11_BLEND_OP_REV_SUBTRACT, // ReverseSubtract
+ D3D11_BLEND_OP_MIN, // Min
+ D3D11_BLEND_OP_MAX, // Max
+ }};
+
+ D3D11_BLEND_DESC blend_desc = {};
+ D3D11_RENDER_TARGET_BLEND_DESC& tgt_desc = blend_desc.RenderTarget[0];
+ tgt_desc.BlendEnable = bs.enable;
+ tgt_desc.RenderTargetWriteMask = bs.write_mask;
+ if (bs.enable)
+ {
+ tgt_desc.SrcBlend = blend_mapping[static_cast(bs.src_blend.GetValue())];
+ tgt_desc.DestBlend = blend_mapping[static_cast(bs.dst_blend.GetValue())];
+ tgt_desc.BlendOp = op_mapping[static_cast(bs.blend_op.GetValue())];
+ tgt_desc.SrcBlendAlpha = blend_mapping[static_cast(bs.src_alpha_blend.GetValue())];
+ tgt_desc.DestBlendAlpha = blend_mapping[static_cast(bs.dst_alpha_blend.GetValue())];
+ tgt_desc.BlendOpAlpha = op_mapping[static_cast(bs.alpha_blend_op.GetValue())];
+ }
+
+ HRESULT hr = m_device->CreateBlendState(&blend_desc, dbs.GetAddressOf());
+ if (FAILED(hr))
+ Log_ErrorPrintf("Failed to create blend state with %08X", hr);
+
+ m_blend_states.emplace(bs.key, dbs);
+ return dbs;
+}
+
+D3D11Device::ComPtr D3D11Device::GetInputLayout(const GPUPipeline::InputLayout& il,
+ const D3D11Shader* vs)
+{
+ ComPtr dil;
+ const auto it = m_input_layouts.find(il);
+ if (it != m_input_layouts.end())
+ {
+ dil = it->second;
+ return dil;
+ }
+
+ static constexpr std::array(GPUPipeline::VertexAttribute::Semantic::MaxCount)>
+ semantics = {{
+ "POSITION", // Position
+ "TEXCOORD", // Texcoord
+ "COLOR", // Color
+ }};
+
+ static constexpr u32 MAX_COMPONENTS = 4;
+ static constexpr const DXGI_FORMAT
+ format_mapping[static_cast(GPUPipeline::VertexAttribute::Type::MaxCount)][MAX_COMPONENTS] = {
+ {DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_R32G32B32_FLOAT,
+ DXGI_FORMAT_R32G32B32A32_FLOAT}, // Float
+ {DXGI_FORMAT_R8_UINT, DXGI_FORMAT_R8G8_UINT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8B8A8_UINT}, // UInt8
+ {DXGI_FORMAT_R8_SINT, DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8B8A8_SINT}, // SInt8
+ {DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_R8G8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8B8A8_UNORM}, // UNorm8
+ {DXGI_FORMAT_R16_UINT, DXGI_FORMAT_R16G16_UINT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16B16A16_UINT}, // UInt16
+ {DXGI_FORMAT_R16_SINT, DXGI_FORMAT_R16G16_SINT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16B16A16_SINT}, // SInt16
+ {DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R16G16B16A16_UNORM}, // UNorm16
+ {DXGI_FORMAT_R32_UINT, DXGI_FORMAT_R32G32_UINT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R32G32B32A32_UINT}, // UInt32
+ {DXGI_FORMAT_R32_SINT, DXGI_FORMAT_R32G32_SINT, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R32G32B32A32_SINT}, // SInt32
+ };
+
+ D3D11_INPUT_ELEMENT_DESC* elems =
+ static_cast(alloca(sizeof(D3D11_INPUT_ELEMENT_DESC) * il.vertex_attributes.size()));
+ for (size_t i = 0; i < il.vertex_attributes.size(); i++)
+ {
+ const GPUPipeline::VertexAttribute& va = il.vertex_attributes[i];
+ Assert(va.components > 0 && va.components < MAX_COMPONENTS);
+
+ D3D11_INPUT_ELEMENT_DESC& elem = elems[i];
+ elem.SemanticName = semantics[static_cast(va.semantic.GetValue())];
+ elem.SemanticIndex = va.semantic_index;
+ elem.Format = format_mapping[static_cast(va.type.GetValue())][va.components - 1];
+ elem.InputSlot = 0;
+ elem.AlignedByteOffset = va.offset;
+ elem.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
+ elem.InstanceDataStepRate = 0;
+ }
+
+ HRESULT hr = m_device->CreateInputLayout(elems, static_cast(il.vertex_attributes.size()),
+ vs->GetBytecode().data(), vs->GetBytecode().size(), dil.GetAddressOf());
+ if (FAILED(hr))
+ Log_ErrorPrintf("Failed to create input layout with %08X", hr);
+
+ m_input_layouts.emplace(il, dil);
+ return dil;
+}
+
+std::unique_ptr D3D11Device::CreatePipeline(const GPUPipeline::GraphicsConfig& config)
+{
+ ComPtr rs = GetRasterizationState(config.rasterization);
+ ComPtr ds = GetDepthState(config.depth);
+ ComPtr bs = GetBlendState(config.blend);
+
+ static constexpr std::array(GPUPipeline::Primitive::MaxCount)> primitives =
+ {{
+ D3D11_PRIMITIVE_TOPOLOGY_POINTLIST, // Points
+ D3D11_PRIMITIVE_TOPOLOGY_LINELIST, // Lines
+ D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST, // Triangles
+ D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, // TriangleStrips
+ }};
+
+ return std::unique_ptr(
+ new D3D11Pipeline(std::move(rs), std::move(ds), std::move(bs), nullptr,
+ static_cast(config.vertex_shader)->GetD3DVertexShader(),
+ static_cast(config.pixel_shader)->GetD3DPixelShader(),
+ primitives[static_cast(config.primitive)]));
+}
diff --git a/src/core/gpu/d3d11_pipeline.h b/src/core/gpu/d3d11_pipeline.h
new file mode 100644
index 000000000..fbffc8e68
--- /dev/null
+++ b/src/core/gpu/d3d11_pipeline.h
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: 2023 Connor McLaughlin
+// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
+
+#pragma once
+
+#include "gpu_pipeline.h"
+
+#include "common/windows_headers.h"
+
+#include
+
+#include
+#include
+
+#include "gsl/span"
+
+class D3D11Device;
+
+class D3D11Pipeline final : public GPUPipeline
+{
+ friend D3D11Device;
+
+ template
+ using ComPtr = Microsoft::WRL::ComPtr;
+
+public:
+ ~D3D11Pipeline() override;
+
+ void SetDebugName(const std::string_view& name) override;
+
+ ALWAYS_INLINE ID3D11RasterizerState* GetRasterizerState() const { return m_rs.Get(); }
+ ALWAYS_INLINE ID3D11DepthStencilState* GetDepthStencilState() const { return m_ds.Get(); }
+ ALWAYS_INLINE ID3D11BlendState* GetBlendState() const { return m_bs.Get(); }
+ ALWAYS_INLINE ID3D11InputLayout* GetInputLayout() const { return m_il.Get(); }
+ ALWAYS_INLINE ID3D11VertexShader* GetVertexShader() const { return m_vs.Get(); }
+ ALWAYS_INLINE ID3D11PixelShader* GetPixelShader() const { return m_ps.Get(); }
+ ALWAYS_INLINE D3D11_PRIMITIVE_TOPOLOGY GetPrimitiveTopology() const { return m_topology; }
+
+ void Bind(ID3D11DeviceContext* context);
+
+private:
+ D3D11Pipeline(ComPtr rs, ComPtr ds, ComPtr bs,
+ ComPtr il, ComPtr vs, ComPtr ps,
+ D3D11_PRIMITIVE_TOPOLOGY topology);
+
+ ComPtr m_rs;
+ ComPtr m_ds;
+ ComPtr m_bs;
+ ComPtr m_il;
+ ComPtr m_vs;
+ ComPtr m_ps;
+ D3D11_PRIMITIVE_TOPOLOGY m_topology;
+};
diff --git a/src/core/gpu/d3d11_shader.cpp b/src/core/gpu/d3d11_shader.cpp
new file mode 100644
index 000000000..cc124678b
--- /dev/null
+++ b/src/core/gpu/d3d11_shader.cpp
@@ -0,0 +1,113 @@
+// SPDX-FileCopyrightText: 2023 Connor McLaughlin
+// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
+
+#include "d3d11_shader.h"
+#include "d3d11/shader_compiler.h"
+#include "d3d11_device.h"
+
+#include "common/assert.h"
+
+D3D11Shader::D3D11Shader(Stage stage, Microsoft::WRL::ComPtr shader)
+ : GPUShader(stage), m_shader(std::move(shader))
+{
+}
+
+D3D11Shader::~D3D11Shader() = default;
+
+ID3D11VertexShader* D3D11Shader::GetD3DVertexShader() const
+{
+ DebugAssert(m_stage == Stage::Vertex);
+ return static_cast(m_shader.Get());
+}
+
+ID3D11PixelShader* D3D11Shader::GetD3DPixelShader() const
+{
+ DebugAssert(m_stage == Stage::Pixel);
+ return static_cast(m_shader.Get());
+}
+
+ID3D11ComputeShader* D3D11Shader::GetD3DComputeShader() const
+{
+ DebugAssert(m_stage == Stage::Compute);
+ return static_cast(m_shader.Get());
+}
+
+void D3D11Shader::SetDebugName(const std::string_view& name)
+{
+ Panic("Implement me");
+}
+
+std::unique_ptr D3D11Device::CreateShaderFromBinary(GPUShader::Stage stage, gsl::span data)
+{
+ ComPtr shader;
+ std::vector bytecode;
+ switch (stage)
+ {
+ case GPUShader::Stage::Vertex:
+ shader = D3D11::ShaderCompiler::CreateVertexShader(D3D11Device::GetD3DDevice(), data.data(), data.size());
+ bytecode.resize(data.size());
+ std::memcpy(bytecode.data(), data.data(), data.size());
+ break;
+
+ case GPUShader::Stage::Pixel:
+ shader = D3D11::ShaderCompiler::CreatePixelShader(D3D11Device::GetD3DDevice(), data.data(), data.size());
+ break;
+
+ case GPUShader::Stage::Compute:
+ shader = D3D11::ShaderCompiler::CreateComputeShader(D3D11Device::GetD3DDevice(), data.data(), data.size());
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ if (!shader)
+ return {};
+
+ return std::unique_ptr(new D3D11Shader(stage, std::move(shader), std::move(bytecode)));
+}
+
+std::unique_ptr D3D11Device::CreateShaderFromSource(GPUShader::Stage stage, const std::string_view& source,
+ std::vector* out_binary /* = nullptr */)
+{
+ // TODO: This shouldn't be dependent on build type.
+#ifdef _DEBUG
+ constexpr bool debug = true;
+#else
+ constexpr bool debug = false;
+#endif
+
+ ComPtr blob;
+ switch (stage)
+ {
+ case GPUShader::Stage::Vertex:
+ blob = D3D11::ShaderCompiler::CompileShader(D3D11::ShaderCompiler::Type::Vertex, m_device->GetFeatureLevel(),
+ source, debug);
+ break;
+
+ case GPUShader::Stage::Pixel:
+ blob = D3D11::ShaderCompiler::CompileShader(D3D11::ShaderCompiler::Type::Pixel, m_device->GetFeatureLevel(),
+ source, debug);
+ break;
+
+ case GPUShader::Stage::Compute:
+ blob = D3D11::ShaderCompiler::CompileShader(D3D11::ShaderCompiler::Type::Compute, m_device->GetFeatureLevel(),
+ source, debug);
+ break;
+
+ default:
+ UnreachableCode();
+ break;
+ }
+
+ if (out_binary)
+ {
+ const size_t size = blob->GetBufferSize();
+ out_binary->resize(size);
+ std::memcpy(out_binary->data(), blob->GetBufferPointer(), size);
+ }
+
+ return CreateShaderFromBinary(
+ stage, gsl::span(static_cast(blob->GetBufferPointer()), blob->GetBufferSize()));
+}
diff --git a/src/core/gpu/d3d11_shader.h b/src/core/gpu/d3d11_shader.h
new file mode 100644
index 000000000..e37406a69
--- /dev/null
+++ b/src/core/gpu/d3d11_shader.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: 2023 Connor McLaughlin
+// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
+
+#pragma once
+
+#include "gpu_shader.h"
+
+#include "common/windows_headers.h"
+
+#include
+#include
+
+#include
+#include
+
+#include "gsl/span"
+
+class D3D11Device;
+
+class D3D11Shader final : public GPUShader
+{
+ friend D3D11Device;
+
+public:
+ ~D3D11Shader() override;
+
+ ID3D11VertexShader* GetD3DVertexShader() const;
+ ID3D11PixelShader* GetD3DPixelShader() const;
+ ID3D11ComputeShader* GetD3DComputeShader() const;
+
+ ALWAYS_INLINE const std::vector& GetBytecode() const { return m_bytecode; }
+
+ void SetDebugName(const std::string_view& name) override;
+
+private:
+ D3D11Shader(Stage stage, Microsoft::WRL::ComPtr shader, std::vector bytecode);
+
+ Microsoft::WRL::ComPtr m_shader;
+ std::vector m_bytecode; // only for VS
+};
diff --git a/src/core/gpu/gpu_device.cpp b/src/core/gpu/gpu_device.cpp
index ad1d58fe2..d3894b6f2 100644
--- a/src/core/gpu/gpu_device.cpp
+++ b/src/core/gpu/gpu_device.cpp
@@ -6,6 +6,7 @@
#include "common/align.h"
#include "common/assert.h"
#include "common/file_system.h"
+#include "common/hash_combine.h"
#include "common/log.h"
#include "common/string_util.h"
#include "common/timer.h"
@@ -22,6 +23,75 @@ Log_SetChannel(GPUDevice);
std::unique_ptr g_host_display;
+size_t GPUPipeline::InputLayoutHash::operator()(const InputLayout& il) const
+{
+ std::size_t h = 0;
+ hash_combine(h, il.vertex_attributes.size(), il.vertex_stride);
+
+ for (const VertexAttribute& va : il.vertex_attributes)
+ hash_combine(h, va.key);
+
+ return h;
+}
+
+bool GPUPipeline::InputLayout::operator==(const InputLayout& rhs) const
+{
+ return (vertex_stride == rhs.vertex_stride && vertex_attributes.size() == rhs.vertex_attributes.size() &&
+ std::memcmp(vertex_attributes.data(), rhs.vertex_attributes.data(),
+ sizeof(VertexAttribute) * rhs.vertex_attributes.size()) == 0);
+}
+
+bool GPUPipeline::InputLayout::operator!=(const InputLayout& rhs) const
+{
+ return (vertex_stride != rhs.vertex_stride ||
+ vertex_attributes.size() != rhs.vertex_attributes.size() &&
+ std::memcmp(vertex_attributes.data(), rhs.vertex_attributes.data(),
+ sizeof(VertexAttribute) * rhs.vertex_attributes.size()) != 0);
+}
+
+GPUPipeline::RasterizationState GPUPipeline::RasterizationState::GetNoCullState()
+{
+ RasterizationState ret = {};
+ ret.cull_mode = CullMode::None;
+ return ret;
+}
+
+GPUPipeline::DepthState GPUPipeline::DepthState::GetNoTestsState()
+{
+ DepthState ret = {};
+ ret.depth_test = DepthFunc::Always;
+ return ret;
+}
+
+GPUPipeline::DepthState GPUPipeline::DepthState::GetAlwaysWriteState()
+{
+ DepthState ret = {};
+ ret.depth_test = DepthFunc::Always;
+ ret.depth_write = true;
+ return ret;
+}
+
+GPUPipeline::BlendState GPUPipeline::BlendState::GetNoBlendingState()
+{
+ BlendState ret = {};
+ ret.write_mask = 0xf;
+ return ret;
+}
+
+GPUPipeline::BlendState GPUPipeline::BlendState::GetAlphaBlendingState()
+{
+ BlendState ret = {};
+ ret.enable = true;
+ ret.src_blend = BlendFunc::SrcAlpha;
+ ret.dst_blend = BlendFunc::InvSrcAlpha;
+ ret.blend_op = BlendOp::Add;
+ ret.src_alpha_blend = BlendFunc::One;
+ ret.dst_alpha_blend = BlendFunc::Zero;
+ ret.alpha_blend_op = BlendOp::Add;
+ ret.write_mask = 0xf;
+ return ret;
+}
+
GPUDevice::~GPUDevice() = default;
RenderAPI GPUDevice::GetPreferredAPI()
@@ -54,6 +124,28 @@ void GPUDevice::ResolveTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32
UnreachableCode();
}
+std::unique_ptr GPUDevice::CreateShaderFromBinary(GPUShader::Stage stage, gsl::span data)
+{
+ // TODO: REMOVE ME
+ UnreachableCode();
+ return {};
+}
+
+std::unique_ptr GPUDevice::CreateShaderFromSource(GPUShader::Stage stage, const std::string_view& source,
+ std::vector* out_binary /* = nullptr */)
+{
+ // TODO: REMOVE ME
+ UnreachableCode();
+ return {};
+}
+
+std::unique_ptr GPUDevice::CreatePipeline(const GPUPipeline::GraphicsConfig& config)
+{
+ // TODO: REMOVE ME
+ UnreachableCode();
+ return {};
+}
+
bool GPUDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate)
{
if (!mode.empty())
diff --git a/src/core/gpu/gpu_device.h b/src/core/gpu/gpu_device.h
index 5530927b8..c48f2687c 100644
--- a/src/core/gpu/gpu_device.h
+++ b/src/core/gpu/gpu_device.h
@@ -2,10 +2,16 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
+#include "gpu_pipeline.h"
+#include "gpu_shader.h"
+#include "gpu_texture.h"
+
#include "common/rectangle.h"
#include "common/types.h"
#include "common/window_info.h"
-#include "gpu_texture.h"
+
+#include "gsl/span"
+
#include
#include
#include
@@ -101,6 +107,12 @@ public:
GPUTexture* src, u32 src_x, u32 src_y, u32 src_layer, u32 src_level, u32 width,
u32 height);
+ /// Shader abstraction.
+ virtual std::unique_ptr CreateShaderFromBinary(GPUShader::Stage stage, gsl::span data);
+ virtual std::unique_ptr CreateShaderFromSource(GPUShader::Stage stage, const std::string_view& source,
+ std::vector* out_binary = nullptr);
+ virtual std::unique_ptr CreatePipeline(const GPUPipeline::GraphicsConfig& config);
+
/// Returns false if the window was completely occluded.
virtual bool Render(bool skip_present) = 0;
diff --git a/src/core/gpu/gpu_pipeline.h b/src/core/gpu/gpu_pipeline.h
new file mode 100644
index 000000000..abbfb7457
--- /dev/null
+++ b/src/core/gpu/gpu_pipeline.h
@@ -0,0 +1,199 @@
+// SPDX-FileCopyrightText: 2023 Connor McLaughlin
+// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
+
+#pragma once
+
+#include "gpu_texture.h"
+
+#include "common/bitfield.h"
+#include "common/types.h"
+
+#include "gsl/span"
+
+#include
+
+class GPUShader;
+
+class GPUPipeline
+{
+public:
+ enum class Layout : u8
+ {
+ // 128 byte UBO via push constants, 1 texture.
+ SingleTexture,
+
+ MaxCount
+ };
+
+ enum class Primitive : u8
+ {
+ Points,
+ Lines,
+ Triangles,
+ TriangleStrips,
+
+ MaxCount
+ };
+
+ union VertexAttribute
+ {
+ enum class Semantic : u8
+ {
+ Position,
+ Texcoord,
+ Color,
+
+ MaxCount
+ };
+
+ enum class Type : u8
+ {
+ Float,
+ UInt8,
+ SInt8,
+ UNorm8,
+ UInt16,
+ SInt16,
+ UNorm16,
+ UInt32,
+ SInt32,
+
+ MaxCount
+ };
+
+ BitField semantic;
+ BitField semantic_index;
+ BitField type;
+ BitField components;
+ BitField offset;
+ u32 key;
+ };
+
+ struct InputLayout
+ {
+ gsl::span vertex_attributes;
+ u32 vertex_stride;
+
+ bool operator==(const InputLayout& rhs) const;
+ bool operator!=(const InputLayout& rhs) const;
+ };
+
+ struct InputLayoutHash
+ {
+ size_t operator()(const InputLayout& il) const;
+ };
+
+ enum class CullMode : u8
+ {
+ None,
+ Front,
+ Back,
+
+ MaxCount
+ };
+
+ enum class DepthFunc : u8
+ {
+ Never,
+ Always,
+ Less,
+ LessEqual,
+ Greater,
+ GreaterEqual,
+ Equal,
+
+ MaxCount
+ };
+
+ enum class BlendFunc : u8
+ {
+ Zero,
+ One,
+ SrcColor,
+ InvSrcColor,
+ DstColor,
+ InvDstColor,
+ SrcAlpha,
+ InvSrcAlpha,
+ SrcAlpha1,
+ InvSrcAlpha1,
+ DstAlpha,
+ InvDstAlpha,
+
+ MaxCount
+ };
+
+ enum class BlendOp : u8
+ {
+ Add,
+ Subtract,
+ ReverseSubtract,
+ Min,
+ Max,
+
+ MaxCount
+ };
+
+ union RasterizationState
+ {
+ BitField cull_mode;
+ u8 key;
+
+ static RasterizationState GetNoCullState();
+ };
+
+ struct DepthState
+ {
+ BitField depth_test;
+ BitField depth_write;
+ u8 key;
+
+ static DepthState GetNoTestsState();
+ static DepthState GetAlwaysWriteState();
+ };
+
+ struct BlendState
+ {
+ BitField enable;
+ BitField src_blend;
+ BitField src_alpha_blend;
+ BitField dst_blend;
+ BitField dst_alpha_blend;
+ BitField blend_op;
+ BitField alpha_blend_op;
+ BitField write_r;
+ BitField write_g;
+ BitField write_b;
+ BitField write_a;
+ BitField write_mask;
+ u32 key;
+
+ static BlendState GetNoBlendingState();
+ static BlendState GetAlphaBlendingState();
+ };
+
+ struct GraphicsConfig
+ {
+ Layout layout;
+
+ Primitive primitive;
+ InputLayout input_layout;
+
+ RasterizationState rasterization;
+ DepthState depth;
+ BlendState blend;
+
+ const GPUShader* vertex_shader;
+ const GPUShader* pixel_shader;
+
+ GPUTexture::Format color_format;
+ GPUTexture::Format depth_format;
+ u32 samples;
+ bool per_sample_shading;
+ };
+
+ GPUPipeline() = default;
+ virtual ~GPUPipeline() = default;
+
+ virtual void SetDebugName(const std::string_view& name) = 0;
+};
diff --git a/src/core/gpu/gpu_shader.h b/src/core/gpu/gpu_shader.h
new file mode 100644
index 000000000..84183edec
--- /dev/null
+++ b/src/core/gpu/gpu_shader.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2023 Connor McLaughlin
+// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
+
+#pragma once
+
+#include
+
+#include "common/types.h"
+
+class GPUShader
+{
+public:
+ enum class Stage
+ {
+ Vertex,
+ Pixel,
+ Compute
+ };
+
+ GPUShader(Stage stage) : m_stage(stage) {}
+ virtual ~GPUShader() = default;
+
+ ALWAYS_INLINE Stage GetStage() const { return m_stage; }
+
+ virtual void SetDebugName(const std::string_view& name) = 0;
+
+protected:
+ Stage m_stage;
+};