Files
ppsspp/Common/GPU/D3D11/thin3d_d3d11.cpp
T
LibretroAdmin 664f6ff5ad D3D11: work on a context that refuses ID3D11DeviceContext1
D3D11DrawContext takes its base context from the deviceContext
argument, and the blend state is bound through the base context, which
accepts an ID3D11BlendState1 as is. ID3D11DeviceContext1 is optional:
RetroArch's threaded video hands v1 cores a deferred-context proxy that
answers ID3D11DeviceContext only, and the core now starts on it.
2026-10-13 02:23:48 +02:00

1993 lines
68 KiB
C++

#include "ppsspp_config.h"
#include "Common/GPU/thin3d.h"
#if PPSSPP_PLATFORM(UWP)
#define ptr_D3DCompile D3DCompile
#else
#include "Common/GPU/D3D11/D3D11Loader.h"
#endif
#include "Common/System/Display.h"
#include "Common/Data/Convert/ColorConv.h"
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/TimeUtil.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
#include <map>
#include <cfloat>
#include <D3Dcommon.h>
#ifndef __LIBRETRO__ // their build server uses an old SDK
#include <dxgi1_5.h>
#endif
#include <d3d11.h>
#include <d3d11_1.h>
#include <D3Dcompiler.h>
#include <wrl/client.h>
using namespace Microsoft::WRL;
namespace Draw {
static constexpr int MAX_BOUND_TEXTURES = 8;
// A problem is that we can't get the D3Dcompiler.dll without using a later SDK than 7.1, which was the last that
// supported XP. A possible solution might be here:
// https://tedwvc.wordpress.com/2014/01/01/how-to-target-xp-with-vc2012-or-vc2013-and-continue-to-use-the-windows-8-x-sdk/
class D3D11Pipeline;
class D3D11BlendState;
class D3D11DepthStencilState;
class D3D11SamplerState;
class D3D11Buffer;
class D3D11RasterState;
// This must stay POD for the memcmp to work reliably.
struct D3D11DepthStencilKey {
DepthStencilStateDesc desc;
u8 writeMask;
u8 compareMask;
bool operator < (const D3D11DepthStencilKey &other) const {
return memcmp(this, &other, sizeof(D3D11DepthStencilKey)) < 0;
}
};
class D3D11DepthStencilState : public DepthStencilState {
public:
~D3D11DepthStencilState() = default;
DepthStencilStateDesc desc;
};
// A D3D11Framebuffer is a D3D11Framebuffer plus all the textures it owns.
class D3D11Framebuffer : public Framebuffer {
public:
D3D11Framebuffer(int width, int height) {
width_ = width;
height_ = height;
}
~D3D11Framebuffer() {
}
ComPtr<ID3D11Texture2D> colorTex;
ComPtr<ID3D11RenderTargetView> colorRTView;
ComPtr<ID3D11ShaderResourceView> colorSRView;
ComPtr<ID3D11ShaderResourceView> depthSRView;
ComPtr<ID3D11ShaderResourceView> stencilSRView;
DXGI_FORMAT colorFormat = DXGI_FORMAT_UNKNOWN;
ComPtr<ID3D11Texture2D> depthStencilTex;
ComPtr<ID3D11DepthStencilView> depthStencilRTView;
DXGI_FORMAT depthStencilFormat = DXGI_FORMAT_UNKNOWN;
};
class D3D11DrawContext : public DrawContext {
public:
D3D11DrawContext(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext, ComPtr<ID3D11Device1> device1, ComPtr<ID3D11DeviceContext1> deviceContext1, ComPtr<IDXGISwapChain> swapChain, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector<std::string> deviceList, int maxInflightFrames);
~D3D11DrawContext();
const DeviceCaps &GetDeviceCaps() const override {
return caps_;
}
std::vector<std::string> GetDeviceList() const override {
return deviceList_;
}
uint32_t GetSupportedShaderLanguages() const override {
return (uint32_t)ShaderLanguage::HLSL_D3D11;
}
uint32_t GetDataFormatSupport(DataFormat fmt) const override;
InputLayout *CreateInputLayout(const InputLayoutDesc &desc) override;
DepthStencilState *CreateDepthStencilState(const DepthStencilStateDesc &desc) override;
BlendState *CreateBlendState(const BlendStateDesc &desc) override;
SamplerState *CreateSamplerState(const SamplerStateDesc &desc) override;
RasterState *CreateRasterState(const RasterStateDesc &desc) override;
Buffer *CreateBuffer(size_t size, uint32_t usageFlags) override;
Pipeline *CreateGraphicsPipeline(const PipelineDesc &desc, const char *tag) override;
Texture *CreateTexture(const TextureDesc &desc) override;
ShaderModule *CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t dataSize, const char *tag) override;
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override;
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, Aspect aspects, const char *tag) override;
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, Aspect aspects, FBBlitFilter filter, const char *tag) override;
bool CopyFramebufferToMemory(Framebuffer *src, Aspect channelBits, int x, int y, int w, int h, Draw::DataFormat format, void *pixels, int pixelStride, ReadbackMode mode, const char *tag) override;
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
void BindFramebufferAsTexture(Framebuffer *fbo, int binding, Aspect channelBit, int layer) override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
void Invalidate(InvalidationFlags flags) override;
void BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) override;
void BindNativeTexture(int index, void *nativeTexture) override;
void BindSamplerStates(int start, int count, SamplerState **states) override;
void BindVertexBuffer(Buffer *buffers, int offset) override;
void BindIndexBuffer(Buffer *indexBuffer, int offset) override;
void BindPipeline(Pipeline *pipeline) override;
void UpdateDynamicUniformBuffer(const void *ub, size_t size) override;
// Raster state
void SetScissorRect(int left, int top, int width, int height) override;
void SetViewport(const Viewport &viewport) override;
void SetBlendFactor(float color[4]) override {
if (0 != memcmp(blendFactor_, color, sizeof(float) * 4)) {
memcpy(blendFactor_, color, sizeof(float) * 4);
blendFactorDirty_ = true;
}
}
void SetStencilParams(uint8_t refValue, uint8_t writeMask, uint8_t compareMask) override {
stencilRef_ = refValue;
stencilWriteMask_ = writeMask;
stencilCompareMask_ = compareMask;
stencilDirty_ = true;
}
void Draw(int vertexCount, int offset) override;
void DrawIndexed(int indexCount, int offset) override;
void DrawUP(const void *vdata, int vertexCount) override;
void DrawIndexedUP(const void *vdata, int vertexCount, const void *idata, int indexCount) override;
void DrawIndexedClippedBatchUP(const void *vdata, int vertexCount, const void *idata, int indexCount, Slice<ClippedDraw> draws, const void *ub, size_t ubSize) override;
void Clear(Aspect mask, uint32_t colorval, float depthVal, int stencilVal) override;
void BeginFrame(DebugFlags debugFlags) override;
void EndFrame() override;
void Present(PresentMode presentMode) override;
PresentMode GetCurrentPresentMode() const override {
if (currentInterval_ == 1) {
return PresentMode::FIFO;
} else {
return PresentMode::IMMEDIATE;
}
}
int GetFrameCount() override { return frameCount_; }
std::string GetInfoString(InfoField info) const override {
switch (info) {
case InfoField::APIVERSION: return "Direct3D 11";
case InfoField::VENDORSTRING: return adapterDesc_;
case InfoField::VENDOR: return "";
case InfoField::DRIVER: return "-";
case InfoField::SHADELANGVERSION:
switch (featureLevel_) {
case D3D_FEATURE_LEVEL_9_1: return "Feature Level 9.1";
case D3D_FEATURE_LEVEL_9_2: return "Feature Level 9.2";
case D3D_FEATURE_LEVEL_9_3: return "Feature Level 9.3";
case D3D_FEATURE_LEVEL_10_0: return "Feature Level 10.0";
case D3D_FEATURE_LEVEL_10_1: return "Feature Level 10.1";
case D3D_FEATURE_LEVEL_11_0: return "Feature Level 11.0";
case D3D_FEATURE_LEVEL_11_1: return "Feature Level 11.1";
case D3D_FEATURE_LEVEL_12_0: return "Feature Level 12.0";
case D3D_FEATURE_LEVEL_12_1: return "Feature Level 12.1";
#ifndef __LIBRETRO__
case D3D_FEATURE_LEVEL_1_0_CORE: return "Feature Level 1.0 Core"; // This is for compute-only devices. Useless for us.
case D3D_FEATURE_LEVEL_12_2: return "Feature Level 12.2";
#endif
default: return "Feature Level X.X";
}
return "Unknown feature level";
case InfoField::APINAME: return "Direct3D 11";
default: return "?";
}
}
uint64_t GetNativeObject(NativeObject obj, void *srcObject) override;
void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override;
void SetInvalidationCallback(InvalidationCallback callback) override {
invalidationCallback_ = callback;
}
private:
void ApplyCurrentState();
HRESULT GetCachedDepthStencilState(const D3D11DepthStencilState *state, uint8_t stencilWriteMask, uint8_t stencilCompareMask, ID3D11DepthStencilState **);
HWND hWnd_;
ComPtr<ID3D11Device> device_;
ComPtr<ID3D11Device1> device1_;
ComPtr<ID3D11DeviceContext> context_;
ComPtr<ID3D11DeviceContext1> context1_;
ComPtr<IDXGISwapChain> swapChain_;
bool swapChainTearingSupported_ = false;
ID3D11Texture2D *bbRenderTargetTex_ = nullptr; // NOT OWNED
ID3D11RenderTargetView *bbRenderTargetView_ = nullptr ; // NOT OWNED
// Strictly speaking we don't need a depth buffer for the backbuffer.
ComPtr<ID3D11Texture2D> bbDepthStencilTex_;
ComPtr<ID3D11DepthStencilView> bbDepthStencilView_;
AutoRef<Framebuffer> curRenderTarget_;
ComPtr<ID3D11RenderTargetView> curRenderTargetView_;
ComPtr<ID3D11DepthStencilView> curDepthStencilView_;
// Needed to rotate stencil/viewport rectangles properly
int bbWidth_ = 0;
int bbHeight_ = 0;
int curRTWidth_ = 0;
int curRTHeight_ = 0;
AutoRef<D3D11Pipeline> curPipeline_;
DeviceCaps caps_{};
AutoRef<D3D11BlendState> curBlend_;
AutoRef<D3D11DepthStencilState> curDepthStencil_;
AutoRef<D3D11RasterState> curRaster_;
std::map<D3D11DepthStencilKey, ComPtr<ID3D11DepthStencilState>> depthStencilCache_;
ComPtr<ID3D11InputLayout> curInputLayout_;
ComPtr<ID3D11VertexShader> curVS_;
ComPtr<ID3D11PixelShader> curPS_;
D3D11_PRIMITIVE_TOPOLOGY curTopology_ = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
ComPtr<ID3D11Buffer> nextVertexBuffer_;
UINT nextVertexBufferOffset_ = 0;
bool dirtyIndexBuffer_ = false;
ComPtr<ID3D11Buffer> nextIndexBuffer_;
UINT nextIndexBufferOffset_ = 0;
InvalidationCallback invalidationCallback_;
int frameCount_ = FRAME_TIME_HISTORY_LENGTH;
// Dynamic state
float blendFactor_[4]{};
bool blendFactorDirty_ = false;
uint8_t stencilRef_ = 0;
uint8_t stencilWriteMask_ = 0xFF;
uint8_t stencilCompareMask_ = 0xFF;
bool stencilDirty_ = true;
// Temporaries
ComPtr<ID3D11Texture2D> packTexture_;
Buffer *upBuffer_ = nullptr;
Buffer *upIBuffer_ = nullptr;
// System info
D3D_FEATURE_LEVEL featureLevel_;
std::string adapterDesc_;
std::vector<std::string> deviceList_;
int currentInterval_ = -1;
};
D3D11DrawContext::D3D11DrawContext(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext, ComPtr<ID3D11Device1> device1, ComPtr<ID3D11DeviceContext1> deviceContext1, ComPtr<IDXGISwapChain> swapChain, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector<std::string> deviceList, int maxInflightFrames)
: hWnd_(hWnd),
device_(device),
context_(deviceContext),
device1_(device1),
context1_(deviceContext1),
featureLevel_(featureLevel),
swapChain_(swapChain),
deviceList_(std::move(deviceList)) {
caps_.coordConvention = CoordConvention::Direct3D11;
caps_.fragmentShaderFullPrecisionFloat = true;
switch (featureLevel_) {
case D3D_FEATURE_LEVEL_11_1:
case D3D_FEATURE_LEVEL_11_0:
caps_.maxTextureSize = 16384;
break;
case D3D_FEATURE_LEVEL_10_1:
case D3D_FEATURE_LEVEL_10_0:
caps_.maxTextureSize = 8192;
break;
case D3D_FEATURE_LEVEL_9_3:
caps_.maxTextureSize = 4096;
break;
case D3D_FEATURE_LEVEL_9_2:
case D3D_FEATURE_LEVEL_9_1:
default:
caps_.maxTextureSize = 2048;
break;
}
// Seems like a fair approximation...
caps_.dualSourceBlend = featureLevel_ >= D3D_FEATURE_LEVEL_10_0;
caps_.depthClampSupported = featureLevel_ >= D3D_FEATURE_LEVEL_10_0;
// SV_ClipDistance# seems to be 10+.
if (featureLevel_ >= D3D_FEATURE_LEVEL_10_0) {
caps_.maxClipDistances = 8;
caps_.maxCullDistances = 8;
}
caps_.depthRangeMinusOneToOne = false;
caps_.framebufferBlitSupported = false;
caps_.framebufferCopySupported = true;
caps_.framebufferDepthBlitSupported = false;
caps_.framebufferStencilBlitSupported = false;
caps_.framebufferDepthCopySupported = true;
caps_.framebufferSeparateDepthCopySupported = false; // Though could be emulated with a draw.
caps_.preferredDepthBufferFormat = DataFormat::D24_S8;
caps_.textureDepthSupported = true;
caps_.texture3DSupported = true;
caps_.fragmentShaderInt32Supported = true;
caps_.anisoSupported = true;
caps_.fragmentShaderDepthWriteSupported = true;
caps_.fragmentShaderStencilWriteSupported = false;
caps_.blendMinMaxSupported = true;
caps_.multiSampleLevelsMask = 1; // More could be supported with some work.
caps_.samplerLodControl = true;
caps_.provokingVertexLast = false; // D3D has it first, unfortunately. (and no way to change it).
caps_.presentInstantModeChange = true;
caps_.presentMaxInterval = 4;
caps_.presentModesSupported = PresentMode::FIFO | PresentMode::IMMEDIATE;
D3D11_FEATURE_DATA_D3D11_OPTIONS options{};
HRESULT result = device_->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &options, sizeof(options));
if (SUCCEEDED(result)) {
if (options.OutputMergerLogicOp) {
// Actually, need to check that the format supports logic ops as well.
// Which normal UNORM formats don't seem to do in D3D11. So meh. We can't enable logicOp support.
// caps_.logicOpSupported = true;
}
}
ComPtr<IDXGIDevice> dxgiDevice;
ComPtr<IDXGIAdapter> adapter;
HRESULT hr = device_.As(&dxgiDevice);
if (SUCCEEDED(hr)) {
hr = dxgiDevice->GetAdapter(&adapter);
if (SUCCEEDED(hr)) {
DXGI_ADAPTER_DESC desc;
adapter->GetDesc(&desc);
adapterDesc_ = ConvertWStringToUTF8(desc.Description);
switch (desc.VendorId) {
case 0x10DE: caps_.vendor = GPUVendor::VENDOR_NVIDIA; break;
case 0x1002:
case 0x1022: caps_.vendor = GPUVendor::VENDOR_AMD; break;
case 0x163C:
case 0x8086:
case 0x8087: caps_.vendor = GPUVendor::VENDOR_INTEL; break;
// TODO: There are Windows ARM devices that could have Qualcomm here too.
// Not sure where I'll find the vendor codes for those though...
default:
caps_.vendor = GPUVendor::VENDOR_UNKNOWN;
}
caps_.deviceID = desc.DeviceId;
}
}
caps_.isTilingGPU = false;
#ifndef __LIBRETRO__ // their build server uses an old SDK
if (swapChain_) {
DXGI_SWAP_CHAIN_DESC swapChainDesc;
swapChain_->GetDesc(&swapChainDesc);
if (swapChainDesc.Flags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) {
swapChainTearingSupported_ = true;
}
}
#endif
// Temp texture for read-back of small images. Custom textures are created on demand for larger ones.
// TODO: Should really benchmark if this extra complexity has any benefit.
D3D11_TEXTURE2D_DESC packDesc{};
packDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
packDesc.BindFlags = 0;
packDesc.Width = 512;
packDesc.Height = 512;
packDesc.ArraySize = 1;
packDesc.MipLevels = 1;
packDesc.Usage = D3D11_USAGE_STAGING;
packDesc.SampleDesc.Count = 1;
packDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
hr = device_->CreateTexture2D(&packDesc, nullptr, &packTexture_);
_assert_(SUCCEEDED(hr));
shaderLanguageDesc_.Init(HLSL_D3D11);
const size_t UP_MAX_BYTES = 65536 * 24;
upBuffer_ = D3D11DrawContext::CreateBuffer(UP_MAX_BYTES, BufferUsageFlag::DYNAMIC | BufferUsageFlag::VERTEXDATA);
upIBuffer_ = D3D11DrawContext::CreateBuffer(UP_MAX_BYTES, BufferUsageFlag::DYNAMIC | BufferUsageFlag::INDEXDATA);
ComPtr<IDXGIDevice1> dxgiDevice1;
hr = device_.As(&dxgiDevice1);
if (SUCCEEDED(hr)) {
caps_.setMaxFrameLatencySupported = true;
dxgiDevice1->SetMaximumFrameLatency(maxInflightFrames);
}
}
D3D11DrawContext::~D3D11DrawContext() {
DestroyPresets();
upBuffer_->Release();
upIBuffer_->Release();
// Release references.
ID3D11RenderTargetView *view = nullptr;
context_->OMSetRenderTargets(1, &view, nullptr);
ID3D11ShaderResourceView *srv[2]{};
context_->PSSetShaderResources(0, 2, srv);
}
void D3D11DrawContext::HandleEvent(Event ev, int width, int height, void *param1, void *param2) {
switch (ev) {
case Event::LOST_BACKBUFFER: {
if (curRenderTargetView_.Get() == bbRenderTargetView_ || curDepthStencilView_ == bbDepthStencilView_) {
ID3D11RenderTargetView *view = nullptr;
context_->OMSetRenderTargets(1, &view, nullptr);
curRenderTargetView_.Reset();
curDepthStencilView_.Reset();
}
bbDepthStencilView_.Reset();
bbDepthStencilTex_.Reset();
curRTWidth_ = 0;
curRTHeight_ = 0;
break;
}
case Event::GOT_BACKBUFFER: {
bbRenderTargetView_ = (ID3D11RenderTargetView *)param1;
bbRenderTargetTex_ = (ID3D11Texture2D *)param2;
bbWidth_ = width;
bbHeight_ = height;
// Create matching depth stencil texture. This is not really needed for PPSSPP though,
// and probably not for most other renderers either as you're usually rendering to other render targets and
// then blitting them with a shader to the screen.
D3D11_TEXTURE2D_DESC descDepth{};
descDepth.Width = width;
descDepth.Height = height;
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;
HRESULT hr = device_->CreateTexture2D(&descDepth, nullptr, &bbDepthStencilTex_);
// Create the depth stencil view
D3D11_DEPTH_STENCIL_VIEW_DESC descDSV{};
descDSV.Format = descDepth.Format;
descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
descDSV.Texture2D.MipSlice = 0;
hr = device_->CreateDepthStencilView(bbDepthStencilTex_.Get(), &descDSV, &bbDepthStencilView_);
context_->OMSetRenderTargets(1, &bbRenderTargetView_, bbDepthStencilView_.Get());
curRenderTargetView_ = bbRenderTargetView_;
curDepthStencilView_ = bbDepthStencilView_;
curRTWidth_ = width;
curRTHeight_ = height;
break;
}
case Event::LOST_DEVICE:
case Event::GOT_DEVICE:
case Event::RESIZED:
case Event::PRESENTED:
break;
}
}
void D3D11DrawContext::EndFrame() {
// Fake a submit time.
frameTimeHistory_[frameCount_].firstSubmit = time_now_d();
curPipeline_ = nullptr;
}
void D3D11DrawContext::Present(PresentMode presentMode) {
frameTimeHistory_[frameCount_].queuePresent = time_now_d();
// Safety for libretro
if (swapChain_) {
uint32_t interval = 1;
uint32_t flags = 0;
if (presentMode != PresentMode::FIFO) {
interval = 0;
#ifndef __LIBRETRO__ // their build server uses an old SDK
flags |= swapChainTearingSupported_ ? DXGI_PRESENT_ALLOW_TEARING : 0; // Assume "vsync off" also means "allow tearing"
#endif
}
swapChain_->Present(interval, flags);
currentInterval_ = interval;
}
curRenderTargetView_.Reset();
curDepthStencilView_.Reset();
frameCount_++;
}
void D3D11DrawContext::SetViewport(const Viewport &viewport) {
DisplayRect<float> rc{ viewport.TopLeftX , viewport.TopLeftY, viewport.Width, viewport.Height };
if (curRenderTargetView_.Get() == bbRenderTargetView_) // Only the backbuffer is actually rotated wrong!
RotateRectToDisplay(rc, curRTWidth_, curRTHeight_);
D3D11_VIEWPORT vp;
vp.TopLeftX = rc.x;
vp.TopLeftY = rc.y;
vp.Width = rc.w;
vp.Height = rc.h;
vp.MinDepth = viewport.MinDepth;
vp.MaxDepth = viewport.MaxDepth;
context_->RSSetViewports(1, &vp);
}
void D3D11DrawContext::SetScissorRect(int left, int top, int width, int height) {
_assert_(width >= 0);
_assert_(height >= 0);
DisplayRect<float> frc{ (float)left, (float)top, (float)width, (float)height };
if (curRenderTargetView_.Get() == bbRenderTargetView_) // Only the backbuffer is actually rotated wrong!
RotateRectToDisplay(frc, curRTWidth_, curRTHeight_);
D3D11_RECT rc{};
rc.left = (INT)frc.x;
rc.top = (INT)frc.y;
rc.right = (INT)(frc.x + frc.w);
rc.bottom = (INT)(frc.y + frc.h);
context_->RSSetScissorRects(1, &rc);
}
static const D3D11_COMPARISON_FUNC compareToD3D11[] = {
D3D11_COMPARISON_NEVER,
D3D11_COMPARISON_LESS,
D3D11_COMPARISON_EQUAL,
D3D11_COMPARISON_LESS_EQUAL,
D3D11_COMPARISON_GREATER,
D3D11_COMPARISON_NOT_EQUAL,
D3D11_COMPARISON_GREATER_EQUAL,
D3D11_COMPARISON_ALWAYS
};
static const D3D11_STENCIL_OP stencilOpToD3D11[] = {
D3D11_STENCIL_OP_KEEP,
D3D11_STENCIL_OP_ZERO,
D3D11_STENCIL_OP_REPLACE,
D3D11_STENCIL_OP_INCR_SAT,
D3D11_STENCIL_OP_DECR_SAT,
D3D11_STENCIL_OP_INVERT,
D3D11_STENCIL_OP_INCR,
D3D11_STENCIL_OP_DECR,
};
static DXGI_FORMAT dataFormatToD3D11(DataFormat format) {
switch (format) {
case DataFormat::R32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
case DataFormat::R32G32_FLOAT: return DXGI_FORMAT_R32G32_FLOAT;
case DataFormat::R32G32B32_FLOAT: return DXGI_FORMAT_R32G32B32_FLOAT;
case DataFormat::R32G32B32A32_FLOAT: return DXGI_FORMAT_R32G32B32A32_FLOAT;
case DataFormat::A4R4G4B4_UNORM_PACK16: return DXGI_FORMAT_B4G4R4A4_UNORM;
case DataFormat::A1R5G5B5_UNORM_PACK16: return DXGI_FORMAT_B5G5R5A1_UNORM;
case DataFormat::R5G6B5_UNORM_PACK16: return DXGI_FORMAT_B5G6R5_UNORM;
case DataFormat::R8G8B8A8_UNORM: return DXGI_FORMAT_R8G8B8A8_UNORM;
case DataFormat::R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
case DataFormat::B8G8R8A8_UNORM: return DXGI_FORMAT_B8G8R8A8_UNORM;
case DataFormat::B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
case DataFormat::R16_UNORM: return DXGI_FORMAT_R16_UNORM;
case DataFormat::R16_FLOAT: return DXGI_FORMAT_R16_FLOAT;
case DataFormat::R16G16_FLOAT: return DXGI_FORMAT_R16G16_FLOAT;
case DataFormat::R16G16B16A16_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT;
case DataFormat::D24_S8: return DXGI_FORMAT_D24_UNORM_S8_UINT;
case DataFormat::D16: return DXGI_FORMAT_D16_UNORM;
case DataFormat::D32F: return DXGI_FORMAT_D32_FLOAT;
case DataFormat::D32F_S8: return DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
case DataFormat::BC1_RGBA_UNORM_BLOCK: return DXGI_FORMAT_BC1_UNORM;
case DataFormat::BC2_UNORM_BLOCK: return DXGI_FORMAT_BC2_UNORM;
case DataFormat::BC3_UNORM_BLOCK: return DXGI_FORMAT_BC3_UNORM;
case DataFormat::BC4_UNORM_BLOCK: return DXGI_FORMAT_BC4_UNORM;
case DataFormat::BC5_UNORM_BLOCK: return DXGI_FORMAT_BC5_UNORM;
case DataFormat::BC7_UNORM_BLOCK: return DXGI_FORMAT_BC7_UNORM;
default:
return DXGI_FORMAT_UNKNOWN;
}
}
static D3D11_PRIMITIVE_TOPOLOGY primToD3D11[] = {
D3D11_PRIMITIVE_TOPOLOGY_POINTLIST,
D3D11_PRIMITIVE_TOPOLOGY_LINELIST,
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED,
// Tesselation shader only
D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST, // ???
// These are for geometry shaders only.
D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ,
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ,
};
inline void CopyStencilSide(D3D11_DEPTH_STENCILOP_DESC &side, const StencilSetup &input) {
side.StencilFunc = compareToD3D11[(int)input.compareOp];
side.StencilDepthFailOp = stencilOpToD3D11[(int)input.depthFailOp];
side.StencilFailOp = stencilOpToD3D11[(int)input.failOp];
side.StencilPassOp = stencilOpToD3D11[(int)input.passOp];
}
static const D3D11_BLEND_OP blendOpToD3D11[] = {
D3D11_BLEND_OP_ADD,
D3D11_BLEND_OP_SUBTRACT,
D3D11_BLEND_OP_REV_SUBTRACT,
D3D11_BLEND_OP_MIN,
D3D11_BLEND_OP_MAX,
};
static const D3D11_BLEND blendToD3D11[] = {
D3D11_BLEND_ZERO,
D3D11_BLEND_ONE,
D3D11_BLEND_SRC_COLOR,
D3D11_BLEND_INV_SRC_COLOR,
D3D11_BLEND_DEST_COLOR,
D3D11_BLEND_INV_DEST_COLOR,
D3D11_BLEND_SRC_ALPHA,
D3D11_BLEND_INV_SRC_ALPHA,
D3D11_BLEND_DEST_ALPHA,
D3D11_BLEND_INV_DEST_ALPHA,
D3D11_BLEND_BLEND_FACTOR,
D3D11_BLEND_INV_BLEND_FACTOR,
D3D11_BLEND_BLEND_FACTOR,
D3D11_BLEND_INV_BLEND_FACTOR,
D3D11_BLEND_SRC1_COLOR,
D3D11_BLEND_INV_SRC1_COLOR,
D3D11_BLEND_SRC1_ALPHA,
D3D11_BLEND_INV_SRC1_ALPHA,
};
class D3D11BlendState : public BlendState {
public:
~D3D11BlendState() {
}
ComPtr<ID3D11BlendState> bs;
float blendFactor[4];
};
HRESULT D3D11DrawContext::GetCachedDepthStencilState(const D3D11DepthStencilState *state, uint8_t stencilWriteMask, uint8_t stencilCompareMask,
ID3D11DepthStencilState **ppDepthStencilState) {
D3D11DepthStencilKey key;
key.desc = state->desc;
key.writeMask = stencilWriteMask;
key.compareMask = stencilCompareMask;
auto findResult = depthStencilCache_.find(key);
if (findResult != depthStencilCache_.end()) {
findResult->second->AddRef();
*ppDepthStencilState = findResult->second.Get();
return S_OK;
}
// OK, create and insert.
D3D11_DEPTH_STENCIL_DESC d3ddesc{};
d3ddesc.DepthEnable = state->desc.depthTestEnabled;
d3ddesc.DepthWriteMask = state->desc.depthWriteEnabled ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
d3ddesc.DepthFunc = compareToD3D11[(int)state->desc.depthCompare];
d3ddesc.StencilEnable = state->desc.stencilEnabled;
d3ddesc.StencilReadMask = stencilCompareMask;
d3ddesc.StencilWriteMask = stencilWriteMask;
if (d3ddesc.StencilEnable) {
CopyStencilSide(d3ddesc.FrontFace, state->desc.stencil);
CopyStencilSide(d3ddesc.BackFace, state->desc.stencil);
}
HRESULT hr = device_->CreateDepthStencilState(&d3ddesc, ppDepthStencilState);
if (SUCCEEDED(hr)) {
depthStencilCache_[key] = *ppDepthStencilState;
}
return hr;
}
DepthStencilState *D3D11DrawContext::CreateDepthStencilState(const DepthStencilStateDesc &desc) {
D3D11DepthStencilState *dss = new D3D11DepthStencilState();
dss->desc = desc;
return static_cast<DepthStencilState *>(dss);
}
BlendState *D3D11DrawContext::CreateBlendState(const BlendStateDesc &desc) {
D3D11BlendState *bs = new D3D11BlendState();
D3D11_BLEND_DESC d3ddesc{};
d3ddesc.AlphaToCoverageEnable = FALSE;
d3ddesc.IndependentBlendEnable = FALSE;
d3ddesc.RenderTarget[0].BlendEnable = desc.enabled;
d3ddesc.RenderTarget[0].RenderTargetWriteMask = desc.colorMask;
d3ddesc.RenderTarget[0].BlendOp = blendOpToD3D11[(int)desc.eqCol];
d3ddesc.RenderTarget[0].BlendOpAlpha = blendOpToD3D11[(int)desc.eqAlpha];
d3ddesc.RenderTarget[0].SrcBlend = blendToD3D11[(int)desc.srcCol];
d3ddesc.RenderTarget[0].SrcBlendAlpha = blendToD3D11[(int)desc.srcAlpha];
d3ddesc.RenderTarget[0].DestBlend = blendToD3D11[(int)desc.dstCol];
d3ddesc.RenderTarget[0].DestBlendAlpha = blendToD3D11[(int)desc.dstAlpha];
if (SUCCEEDED(device_->CreateBlendState(&d3ddesc, &bs->bs)))
return bs;
delete bs;
return nullptr;
}
class D3D11RasterState : public RasterState {
public:
~D3D11RasterState() {
}
ComPtr<ID3D11RasterizerState> rs;
};
RasterState *D3D11DrawContext::CreateRasterState(const RasterStateDesc &desc) {
D3D11RasterState *rs = new D3D11RasterState();
D3D11_RASTERIZER_DESC d3ddesc{};
d3ddesc.FillMode = D3D11_FILL_SOLID;
switch (desc.cull) {
case CullMode::BACK: d3ddesc.CullMode = D3D11_CULL_BACK; break;
case CullMode::FRONT: d3ddesc.CullMode = D3D11_CULL_FRONT; break;
default:
case CullMode::NONE: d3ddesc.CullMode = D3D11_CULL_NONE; break;
}
d3ddesc.FrontCounterClockwise = desc.frontFace == Facing::CCW;
d3ddesc.ScissorEnable = true; // We always run with scissor enabled
d3ddesc.DepthClipEnable = true;
if (SUCCEEDED(device_->CreateRasterizerState(&d3ddesc, &rs->rs)))
return rs;
delete rs;
return nullptr;
}
class D3D11SamplerState : public SamplerState {
public:
~D3D11SamplerState() {
}
ComPtr<ID3D11SamplerState> ss;
};
static const D3D11_TEXTURE_ADDRESS_MODE taddrToD3D11[] = {
D3D11_TEXTURE_ADDRESS_WRAP,
D3D11_TEXTURE_ADDRESS_MIRROR,
D3D11_TEXTURE_ADDRESS_CLAMP,
D3D11_TEXTURE_ADDRESS_BORDER,
};
SamplerState *D3D11DrawContext::CreateSamplerState(const SamplerStateDesc &desc) {
D3D11SamplerState *ss = new D3D11SamplerState();
D3D11_SAMPLER_DESC d3ddesc{};
d3ddesc.AddressU = taddrToD3D11[(int)desc.wrapU];
d3ddesc.AddressV = taddrToD3D11[(int)desc.wrapV];
d3ddesc.AddressW = taddrToD3D11[(int)desc.wrapW];
// TODO: Needs improvement
d3ddesc.Filter = desc.magFilter == TextureFilter::LINEAR ? D3D11_FILTER_MIN_MAG_MIP_LINEAR : D3D11_FILTER_MIN_MAG_MIP_POINT;
d3ddesc.MaxAnisotropy = 1.0f; // (UINT)desc.maxAniso;
d3ddesc.MinLOD = -FLT_MAX;
d3ddesc.MaxLOD = FLT_MAX;
d3ddesc.ComparisonFunc = compareToD3D11[(int)desc.shadowCompareFunc];
for (int i = 0; i < 4; i++) {
d3ddesc.BorderColor[i] = 1.0f;
}
if (SUCCEEDED(device_->CreateSamplerState(&d3ddesc, &ss->ss)))
return ss;
delete ss;
return nullptr;
}
// Input layout creation is delayed to pipeline creation, as we need the vertex shader bytecode.
class D3D11InputLayout : public InputLayout {
public:
D3D11InputLayout() {}
InputLayoutDesc desc;
std::vector<D3D11_INPUT_ELEMENT_DESC> elements;
UINT stride; // type to match function parameter
};
const char *semanticToD3D11(int semantic, UINT *index) {
*index = 0;
switch (semantic) {
case SEM_POSITION: return "POSITION";
case SEM_COLOR0: *index = 0; return "COLOR";
case SEM_COLOR1: *index = 1; return "COLOR";
case SEM_TEXCOORD0: *index = 0; return "TEXCOORD";
case SEM_TEXCOORD1: *index = 1; return "TEXCOORD";
case SEM_NORMAL: return "NORMAL";
case SEM_TANGENT: return "TANGENT";
case SEM_BINORMAL: return "BINORMAL"; // really BITANGENT
default: return "UNKNOWN";
}
}
InputLayout *D3D11DrawContext::CreateInputLayout(const InputLayoutDesc &desc) {
D3D11InputLayout *inputLayout = new D3D11InputLayout();
inputLayout->desc = desc;
// Translate to D3D11 elements;
for (size_t i = 0; i < desc.attributes.size(); i++) {
D3D11_INPUT_ELEMENT_DESC el;
el.AlignedByteOffset = desc.attributes[i].offset;
el.Format = dataFormatToD3D11(desc.attributes[i].format);
el.InstanceDataStepRate = 0;
el.InputSlot = 0;
el.SemanticName = semanticToD3D11(desc.attributes[i].location, &el.SemanticIndex);
el.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
inputLayout->elements.push_back(el);
}
inputLayout->stride = desc.stride;
return inputLayout;
}
class D3D11ShaderModule : public ShaderModule {
public:
D3D11ShaderModule(std::string_view tag) : tag_(tag) { }
~D3D11ShaderModule() {
}
ShaderStage GetStage() const override { return stage; }
std::vector<uint8_t> byteCode_;
ShaderStage stage;
std::string tag_;
ComPtr<ID3D11VertexShader> vs;
ComPtr<ID3D11PixelShader> ps;
};
class D3D11Pipeline : public Pipeline {
public:
~D3D11Pipeline() {
for (D3D11ShaderModule *shaderModule : shaderModules) {
shaderModule->Release();
}
}
AutoRef<D3D11InputLayout> input;
ComPtr<ID3D11InputLayout> il;
AutoRef<D3D11BlendState> blend;
AutoRef<D3D11RasterState> raster;
// Combined with dynamic state to key into cached D3D11DepthStencilState, to emulate dynamic parameters.
AutoRef<D3D11DepthStencilState> depthStencil;
ComPtr<ID3D11VertexShader> vs;
ComPtr<ID3D11PixelShader> ps;
D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
std::vector<D3D11ShaderModule *> shaderModules;
size_t dynamicUniformsSize = 0;
ComPtr<ID3D11Buffer> dynamicUniforms;
};
class D3D11Texture : public Texture {
public:
D3D11Texture(const TextureDesc &desc) {
width_ = desc.width;
height_ = desc.height;
depth_ = desc.depth;
format_ = desc.format;
mipLevels_ = desc.mipLevels;
}
~D3D11Texture() {
}
bool Create(ID3D11DeviceContext *context, ID3D11Device *device, const TextureDesc &desc, bool generateMips);
bool CreateStagingTexture(ID3D11Device *device);
void UpdateTextureLevels(ID3D11DeviceContext *context, ID3D11Device *device, Texture *texture, const uint8_t *const *data, TextureCallback initDataCallback, int numLevels);
void UpdateTextureRegions(ID3D11DeviceContext *context, int level, const TextureRegionUpdate *regions, int numRegions);
ID3D11ShaderResourceView *View() { return view_.Get(); }
private:
bool FillLevel(ID3D11DeviceContext *context, int level, int w, int h, int d, const uint8_t *const *data, TextureCallback initDataCallback);
ComPtr<ID3D11Texture2D> tex_;
ComPtr<ID3D11Texture2D> stagingTex_;
ComPtr<ID3D11ShaderResourceView> view_;
int mipLevels_ = 0;
};
bool D3D11Texture::FillLevel(ID3D11DeviceContext *context, int level, int w, int h, int d, const uint8_t *const *data, TextureCallback initDataCallback) {
D3D11_MAPPED_SUBRESOURCE mapped;
HRESULT hr = context->Map(stagingTex_.Get(), level, D3D11_MAP_WRITE, 0, &mapped);
if (!SUCCEEDED(hr)) {
tex_.Reset();
return false;
}
if (!initDataCallback((uint8_t *)mapped.pData, data[level], w, h, d, mapped.RowPitch, mapped.DepthPitch)) {
for (int s = 0; s < d; ++s) {
for (int y = 0; y < h; ++y) {
void *dest = (uint8_t *)mapped.pData + mapped.DepthPitch * s + mapped.RowPitch * y;
uint32_t byteStride = w * (uint32_t)DataFormatSizeInBytes(format_);
const void *src = data[level] + byteStride * (y + h * s);
memcpy(dest, src, byteStride);
}
}
}
context->Unmap(stagingTex_.Get(), level);
return true;
}
bool D3D11Texture::CreateStagingTexture(ID3D11Device *device) {
if (stagingTex_)
return true;
D3D11_TEXTURE2D_DESC descColor{};
descColor.Width = width_;
descColor.Height = height_;
descColor.MipLevels = mipLevels_;
descColor.ArraySize = 1;
descColor.Format = dataFormatToD3D11(format_);
descColor.SampleDesc.Count = 1;
descColor.SampleDesc.Quality = 0;
descColor.Usage = D3D11_USAGE_STAGING;
descColor.BindFlags = 0;
descColor.MiscFlags = 0;
descColor.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
return SUCCEEDED(device->CreateTexture2D(&descColor, nullptr, &stagingTex_));
}
bool D3D11Texture::Create(ID3D11DeviceContext *context, ID3D11Device *device, const TextureDesc &desc, bool generateMips) {
D3D11_TEXTURE2D_DESC descColor{};
descColor.Width = desc.width;
descColor.Height = desc.height;
descColor.MipLevels = desc.mipLevels;
descColor.ArraySize = 1;
descColor.Format = dataFormatToD3D11(desc.format);
descColor.SampleDesc.Count = 1;
descColor.SampleDesc.Quality = 0;
descColor.Usage = D3D11_USAGE_DEFAULT;
descColor.BindFlags = generateMips ? (D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET) : D3D11_BIND_SHADER_RESOURCE;
descColor.MiscFlags = generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
descColor.CPUAccessFlags = 0;
// Make sure we have a staging texture if we'll need it.
if (desc.initDataCallback && !CreateStagingTexture(device)) {
return false;
}
D3D11_SUBRESOURCE_DATA *initDataParam = nullptr;
D3D11_SUBRESOURCE_DATA initData[12]{};
std::vector<uint8_t> initDataBuffer[12];
if (desc.initData.size() && !generateMips && !desc.initDataCallback) {
int w = desc.width;
int h = desc.height;
int d = desc.depth;
for (int i = 0; i < (int)desc.initData.size(); i++) {
uint32_t byteStride = w * (uint32_t)DataFormatSizeInBytes(desc.format);
initData[i].pSysMem = desc.initData[i];
initData[i].SysMemPitch = (UINT)byteStride;
initData[i].SysMemSlicePitch = (UINT)(h * byteStride);
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
initDataParam = initData;
}
HRESULT hr = device->CreateTexture2D(&descColor, initDataParam, &tex_);
if (!SUCCEEDED(hr)) {
tex_ = nullptr;
return false;
}
hr = device->CreateShaderResourceView(tex_.Get(), nullptr, &view_);
if (!SUCCEEDED(hr)) {
return false;
}
if (generateMips && desc.initData.size() >= 1) {
if (desc.initDataCallback) {
if (!FillLevel(context, 0, desc.width, desc.height, desc.depth, desc.initData.data(), desc.initDataCallback)) {
tex_.Reset();
return false;
}
context->CopyResource(tex_.Get(), stagingTex_.Get());
stagingTex_.Reset();
} else {
uint32_t byteStride = desc.width * (uint32_t)DataFormatSizeInBytes(desc.format);
context->UpdateSubresource(tex_.Get(), 0, nullptr, desc.initData[0], byteStride, 0);
}
context->GenerateMips(view_.Get());
} else if (desc.initDataCallback) {
int w = desc.width;
int h = desc.height;
int d = desc.depth;
for (int i = 0; i < (int)desc.initData.size(); i++) {
if (!FillLevel(context, i, w, h, d, desc.initData.data(), desc.initDataCallback)) {
if (i == 0) {
return false;
} else {
break;
}
}
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
context->CopyResource(tex_.Get(), stagingTex_.Get());
stagingTex_.Reset();
}
return true;
}
void D3D11Texture::UpdateTextureLevels(ID3D11DeviceContext *context, ID3D11Device *device, Texture *texture, const uint8_t * const*data, TextureCallback initDataCallback, int numLevels) {
if (!CreateStagingTexture(device)) {
return;
}
int w = width_;
int h = height_;
int d = depth_;
for (int i = 0; i < numLevels; i++) {
if (!FillLevel(context, i, w, h, d, data, initDataCallback)) {
break;
}
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
context->CopyResource(tex_.Get(), stagingTex_.Get());
stagingTex_.Reset();
}
void D3D11Texture::UpdateTextureRegions(ID3D11DeviceContext *context, int level, const TextureRegionUpdate *regions, int numRegions) {
const UINT pixelSize = (UINT)DataFormatSizeInBytes(format_);
for (int i = 0; i < numRegions; i++) {
const TextureRegionUpdate &region = regions[i];
D3D11_BOX box{};
box.left = region.x;
box.top = region.y;
box.front = 0;
box.right = region.x + region.w;
box.bottom = region.y + region.h;
box.back = 1;
const UINT srcStride = region.byteStride ? (UINT)region.byteStride : region.w * pixelSize;
context->UpdateSubresource(tex_.Get(), level, &box, region.data, srcStride, 0);
}
}
Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
if (!(GetDataFormatSupport(desc.format) & FMT_TEXTURE)) {
// D3D11 does not support this format as a texture format.
return nullptr;
}
D3D11Texture *tex = new D3D11Texture(desc);
bool generateMips = desc.generateMips;
if (desc.generateMips && !(GetDataFormatSupport(desc.format) & FMT_AUTOGEN_MIPS)) {
// D3D11 does not support autogenerating mipmaps for this format.
generateMips = false;
}
if (!tex->Create(context_.Get(), device_.Get(), desc, generateMips)) {
tex->Release();
return nullptr;
}
return tex;
}
void D3D11DrawContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
D3D11Texture *tex = (D3D11Texture *)texture;
tex->UpdateTextureLevels(context_.Get(), device_.Get(), texture, data, initDataCallback, numLevels);
}
void D3D11DrawContext::UpdateTextureRegions(Texture *texture, int level, const TextureRegionUpdate *regions, int numRegions) {
D3D11Texture *tex = (D3D11Texture *)texture;
tex->UpdateTextureRegions(context_.Get(), level, regions, numRegions);
}
ShaderModule *D3D11DrawContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t dataSize, const char *tag) {
if (language != ShaderLanguage::HLSL_D3D11) {
ERROR_LOG(Log::G3D, "Unsupported shader language");
return nullptr;
}
const char *vertexModel = "vs_4_0";
const char *fragmentModel = "ps_4_0";
if (featureLevel_ <= D3D_FEATURE_LEVEL_9_3) {
vertexModel = "vs_4_0_level_9_1";
fragmentModel = "ps_4_0_level_9_1";
}
std::string compiled;
std::string errors;
const char *target = nullptr;
switch (stage) {
case ShaderStage::Fragment: target = fragmentModel; break;
case ShaderStage::Vertex: target = vertexModel; break;
case ShaderStage::Compute:
default:
Crash();
break;
}
if (!target) {
return nullptr;
}
ComPtr<ID3DBlob> compiledCode;
ComPtr<ID3DBlob> errorMsgs;
int flags = D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY;
HRESULT result = ptr_D3DCompile(data, dataSize, nullptr, nullptr, nullptr, "main", target, flags, 0, &compiledCode, &errorMsgs);
if (compiledCode) {
compiled = std::string((const char *)compiledCode->GetBufferPointer(), compiledCode->GetBufferSize());
}
if (errorMsgs) {
errors = std::string((const char *)errorMsgs->GetBufferPointer(), errorMsgs->GetBufferSize());
ERROR_LOG(Log::G3D, "Failed compiling %s:", tag);
ERROR_LOG(Log::G3D, "%s", LineNumberString(std::string((const char *)data, dataSize)).c_str());
ERROR_LOG(Log::G3D, "%s", errors.c_str());
}
if (result != S_OK) {
return nullptr;
}
// OK, we can now proceed
data = (const uint8_t *)compiled.c_str();
dataSize = compiled.size();
D3D11ShaderModule *module = new D3D11ShaderModule(tag);
module->stage = stage;
module->byteCode_ = std::vector<uint8_t>(data, data + dataSize);
switch (stage) {
case ShaderStage::Vertex:
result = device_->CreateVertexShader(data, dataSize, nullptr, &module->vs);
break;
case ShaderStage::Fragment:
result = device_->CreatePixelShader(data, dataSize, nullptr, &module->ps);
break;
default:
ERROR_LOG(Log::G3D, "Unsupported shader stage");
result = S_FALSE;
break;
}
if (result == S_OK) {
return module;
} else {
delete module;
return nullptr;
}
return nullptr;
}
Pipeline *D3D11DrawContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char *tag) {
D3D11Pipeline *dPipeline = new D3D11Pipeline();
dPipeline->blend = (D3D11BlendState *)desc.blend;
dPipeline->depthStencil = (D3D11DepthStencilState *)desc.depthStencil;
dPipeline->input = (D3D11InputLayout *)desc.inputLayout;
dPipeline->raster = (D3D11RasterState *)desc.raster;
dPipeline->topology = primToD3D11[(int)desc.prim];
if (desc.uniformDesc) {
dPipeline->dynamicUniformsSize = desc.uniformDesc->uniformBufferSize;
D3D11_BUFFER_DESC bufdesc{};
bufdesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
// We just round up to 16 here. If we get some garbage, that's fine.
bufdesc.ByteWidth = ((UINT)dPipeline->dynamicUniformsSize + 15) & ~15;
bufdesc.StructureByteStride = bufdesc.ByteWidth;
bufdesc.Usage = D3D11_USAGE_DYNAMIC;
bufdesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
HRESULT hr = device_->CreateBuffer(&bufdesc, nullptr, &dPipeline->dynamicUniforms);
if (FAILED(hr)) {
dPipeline->Release();
return nullptr;
}
}
std::vector<D3D11ShaderModule *> shaders;
D3D11ShaderModule *vshader = nullptr;
for (auto iter : desc.shaders) {
iter->AddRef();
D3D11ShaderModule *module = (D3D11ShaderModule *)iter;
shaders.push_back(module);
switch (module->GetStage()) {
case ShaderStage::Vertex:
vshader = module;
dPipeline->vs = module->vs;
break;
case ShaderStage::Fragment:
dPipeline->ps = module->ps;
break;
case ShaderStage::Compute:
break;
}
}
dPipeline->shaderModules = shaders;
if (!vshader) {
// No vertex shader - no graphics
dPipeline->Release();
return nullptr;
}
// Can finally create the input layout
if (dPipeline->input != nullptr) {
const std::vector<D3D11_INPUT_ELEMENT_DESC> &elements = dPipeline->input->elements;
HRESULT hr = device_->CreateInputLayout(elements.data(), (UINT)elements.size(), vshader->byteCode_.data(), vshader->byteCode_.size(), &dPipeline->il);
if (!SUCCEEDED(hr)) {
Crash();
}
} else {
dPipeline->il.Reset();
}
return dPipeline;
}
void D3D11DrawContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) {
if (curPipeline_->dynamicUniformsSize != size) {
Crash();
}
D3D11_MAPPED_SUBRESOURCE map{};
context_->Map(curPipeline_->dynamicUniforms.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &map);
memcpy(map.pData, ub, size);
context_->Unmap(curPipeline_->dynamicUniforms.Get(), 0);
}
void D3D11DrawContext::Invalidate(InvalidationFlags flags) {
if (flags & InvalidationFlags::CACHED_RENDER_STATE) {
// This is a signal to forget all our state caching.
curBlend_ = nullptr;
curDepthStencil_ = nullptr;
curRaster_ = nullptr;
curPS_.Reset();
curVS_.Reset();
curInputLayout_.Reset();
curTopology_ = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
curPipeline_= nullptr;
}
}
void D3D11DrawContext::BindPipeline(Pipeline *pipeline) {
D3D11Pipeline *dPipeline = (D3D11Pipeline *)pipeline;
if (curPipeline_ == dPipeline)
return;
curPipeline_ = dPipeline;
}
void D3D11DrawContext::ApplyCurrentState() {
if (curBlend_ != curPipeline_->blend || blendFactorDirty_) {
context_->OMSetBlendState(curPipeline_->blend->bs.Get(), blendFactor_, 0xFFFFFFFF);
curBlend_ = curPipeline_->blend;
blendFactorDirty_ = false;
}
if (curDepthStencil_ != curPipeline_->depthStencil || stencilDirty_) {
ComPtr<ID3D11DepthStencilState> dss;
GetCachedDepthStencilState(curPipeline_->depthStencil, stencilWriteMask_, stencilCompareMask_, &dss);
context_->OMSetDepthStencilState(dss.Get(), stencilRef_);
curDepthStencil_ = curPipeline_->depthStencil;
stencilDirty_ = false;
}
if (curRaster_ != curPipeline_->raster) {
context_->RSSetState(curPipeline_->raster->rs.Get());
curRaster_ = curPipeline_->raster;
}
if (curInputLayout_ != curPipeline_->il) {
context_->IASetInputLayout(curPipeline_->il.Get());
curInputLayout_ = curPipeline_->il;
}
if (curVS_ != curPipeline_->vs) {
context_->VSSetShader(curPipeline_->vs.Get(), nullptr, 0);
curVS_ = curPipeline_->vs;
}
if (curPS_ != curPipeline_->ps) {
context_->PSSetShader(curPipeline_->ps.Get(), nullptr, 0);
curPS_ = curPipeline_->ps;
}
if (curTopology_ != curPipeline_->topology) {
context_->IASetPrimitiveTopology(curPipeline_->topology);
curTopology_ = curPipeline_->topology;
}
if (curPipeline_->input != nullptr) {
context_->IASetVertexBuffers(0, 1, nextVertexBuffer_.GetAddressOf(), &curPipeline_->input->stride, &nextVertexBufferOffset_);
}
if (dirtyIndexBuffer_) {
context_->IASetIndexBuffer(nextIndexBuffer_.Get(), DXGI_FORMAT_R16_UINT, nextIndexBufferOffset_);
dirtyIndexBuffer_ = false;
}
if (curPipeline_->dynamicUniforms) {
context_->VSSetConstantBuffers(0, 1, curPipeline_->dynamicUniforms.GetAddressOf());
context_->PSSetConstantBuffers(0, 1, curPipeline_->dynamicUniforms.GetAddressOf());
}
}
class D3D11Buffer : public Buffer {
public:
~D3D11Buffer() {
}
ComPtr<ID3D11Buffer> buf;
ComPtr<ID3D11ShaderResourceView> srView;
size_t size;
};
Buffer *D3D11DrawContext::CreateBuffer(size_t size, uint32_t usageFlags) {
D3D11Buffer *b = new D3D11Buffer();
D3D11_BUFFER_DESC desc{};
desc.ByteWidth = (UINT)size;
desc.BindFlags = 0;
if (usageFlags & VERTEXDATA)
desc.BindFlags |= D3D11_BIND_VERTEX_BUFFER;
if (usageFlags & INDEXDATA)
desc.BindFlags |= D3D11_BIND_INDEX_BUFFER;
if (usageFlags & UNIFORM)
desc.BindFlags |= D3D11_BIND_CONSTANT_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.Usage = D3D11_USAGE_DYNAMIC;
b->size = size;
HRESULT hr = device_->CreateBuffer(&desc, nullptr, &b->buf);
if (FAILED(hr)) {
delete b;
return nullptr;
}
return b;
}
void D3D11DrawContext::UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) {
D3D11Buffer *buf = (D3D11Buffer *)buffer;
if ((flags & UPDATE_DISCARD) || (offset == 0 && size == buf->size)) {
// Can just discard the old contents. This is only allowed for DYNAMIC buffers.
D3D11_MAPPED_SUBRESOURCE map;
context_->Map(buf->buf.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &map);
memcpy(map.pData, data, size);
context_->Unmap(buf->buf.Get(), 0);
return;
}
// Should probably avoid this case.
D3D11_BOX box{};
box.left = (UINT)offset;
box.right = (UINT)(offset + size);
box.bottom = 1;
box.back = 1;
context_->UpdateSubresource(buf->buf.Get(), 0, &box, data, 0, 0);
}
void D3D11DrawContext::BindVertexBuffer(Buffer *buffer, int offset) {
// Lazy application
D3D11Buffer *buf = (D3D11Buffer *)buffer;
nextVertexBuffer_ = buf->buf;
nextVertexBufferOffset_ = offset;
}
void D3D11DrawContext::BindIndexBuffer(Buffer *indexBuffer, int offset) {
D3D11Buffer *buf = (D3D11Buffer *)indexBuffer;
// Lazy application
dirtyIndexBuffer_ = true;
nextIndexBuffer_ = buf ? buf->buf : nullptr;
nextIndexBufferOffset_ = buf ? offset : 0;
}
void D3D11DrawContext::Draw(int vertexCount, int offset) {
ApplyCurrentState();
context_->Draw(vertexCount, offset);
}
void D3D11DrawContext::DrawIndexed(int indexCount, int offset) {
ApplyCurrentState();
context_->DrawIndexed(indexCount, offset, 0);
}
void D3D11DrawContext::DrawUP(const void *vdata, int vertexCount) {
int byteSize = vertexCount * curPipeline_->input->stride;
UpdateBuffer(upBuffer_, (const uint8_t *)vdata, 0, byteSize, Draw::UPDATE_DISCARD);
BindVertexBuffer(upBuffer_, 0);
int offset = 0;
Draw(vertexCount, offset);
}
void D3D11DrawContext::DrawIndexedUP(const void *vdata, int vertexCount, const void *idata, int indexCount) {
int vbyteSize = vertexCount * curPipeline_->input->stride;
int ibyteSize = indexCount * sizeof(u16);
UpdateBuffer(upBuffer_, (const uint8_t *)vdata, 0, vbyteSize, Draw::UPDATE_DISCARD);
BindVertexBuffer(upBuffer_, 0);
UpdateBuffer(upIBuffer_, (const uint8_t *)idata, 0, ibyteSize, Draw::UPDATE_DISCARD);
BindIndexBuffer(upIBuffer_, 0);
DrawIndexed(indexCount, 0);
}
void D3D11DrawContext::DrawIndexedClippedBatchUP(const void *vdata, int vertexCount, const void *idata, int indexCount, Slice<ClippedDraw> draws, const void *ub, size_t ubSize) {
if (draws.is_empty() || !vertexCount || !indexCount) {
return;
}
curPipeline_ = (D3D11Pipeline *)draws[0].pipeline;
int vbyteSize = vertexCount * curPipeline_->input->stride;
int ibyteSize = indexCount * sizeof(u16);
UpdateBuffer(upBuffer_, (const uint8_t *)vdata, 0, vbyteSize, Draw::UPDATE_DISCARD);
BindVertexBuffer(upBuffer_, 0);
UpdateBuffer(upIBuffer_, (const uint8_t *)idata, 0, ibyteSize, Draw::UPDATE_DISCARD);
BindIndexBuffer(upIBuffer_, 0);
UpdateDynamicUniformBuffer(ub, ubSize);
ApplyCurrentState();
for (int i = 0; i < draws.size(); i++) {
if (draws[i].pipeline != curPipeline_) {
curPipeline_ = (D3D11Pipeline *)draws[i].pipeline;
ApplyCurrentState();
UpdateDynamicUniformBuffer(ub, ubSize);
}
if (draws[i].bindTexture) {
ComPtr<ID3D11ShaderResourceView> view = ((D3D11Texture *)draws[i].bindTexture)->View();
context_->PSSetShaderResources(0, 1, view.GetAddressOf());
} else if (draws[i].bindFramebufferAsTex) {
ComPtr<ID3D11ShaderResourceView> view = ((D3D11Framebuffer *)draws[i].bindFramebufferAsTex)->colorSRView;
switch (draws[i].aspect) {
case Aspect::DEPTH_BIT:
view = ((D3D11Framebuffer *)draws[i].bindFramebufferAsTex)->depthSRView;
break;
case Aspect::STENCIL_BIT:
view = ((D3D11Framebuffer *)draws[i].bindFramebufferAsTex)->stencilSRView;
break;
default:
break;
}
context_->PSSetShaderResources(0, 1, view.GetAddressOf());
} else if (draws[i].bindNativeTexture) {
ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)draws[i].bindNativeTexture;
context_->PSSetShaderResources(0, 1, &view);
}
ComPtr<ID3D11SamplerState> sstate = ((D3D11SamplerState *)draws[i].samplerState)->ss;
context_->PSSetSamplers(0, 1, sstate.GetAddressOf());
D3D11_RECT rc;
rc.left = draws[i].clipx;
rc.top = draws[i].clipy;
rc.right = draws[i].clipx + draws[i].clipw;
rc.bottom = draws[i].clipy + draws[i].cliph;
context_->RSSetScissorRects(1, &rc);
context_->DrawIndexed(draws[i].indexCount, draws[i].indexOffset, 0);
}
}
uint32_t D3D11DrawContext::GetDataFormatSupport(DataFormat fmt) const {
DXGI_FORMAT giFmt = dataFormatToD3D11(fmt);
if (giFmt == DXGI_FORMAT_UNKNOWN)
return 0;
UINT giSupport = 0;
HRESULT result = device_->CheckFormatSupport(giFmt, &giSupport);
if (FAILED(result))
return 0;
uint32_t support = 0;
if (giSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D)
support |= FMT_TEXTURE;
if (giSupport & D3D11_FORMAT_SUPPORT_RENDER_TARGET)
support |= FMT_RENDERTARGET;
if (giSupport & D3D11_FORMAT_SUPPORT_IA_VERTEX_BUFFER)
support |= FMT_INPUTLAYOUT;
if (giSupport & D3D11_FORMAT_SUPPORT_DEPTH_STENCIL)
support |= FMT_DEPTHSTENCIL;
if (giSupport & D3D11_FORMAT_SUPPORT_MIP_AUTOGEN)
support |= FMT_AUTOGEN_MIPS;
return support;
}
Framebuffer *D3D11DrawContext::CreateFramebuffer(const FramebufferDesc &desc) {
HRESULT hr;
D3D11Framebuffer *fb = new D3D11Framebuffer(desc.width, desc.height);
// We don't (yet?) support multiview for D3D11. Not sure if there's a way to do it.
// Texture arrays are supported but we don't have any other use cases yet.
_dbg_assert_(desc.numLayers == 1);
fb->colorFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
D3D11_TEXTURE2D_DESC descColor{};
descColor.Width = desc.width;
descColor.Height = desc.height;
descColor.MipLevels = 1;
descColor.ArraySize = 1;
descColor.Format = fb->colorFormat;
descColor.SampleDesc.Count = 1;
descColor.SampleDesc.Quality = 0;
descColor.Usage = D3D11_USAGE_DEFAULT;
descColor.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
descColor.CPUAccessFlags = 0;
descColor.MiscFlags = 0;
hr = device_->CreateTexture2D(&descColor, nullptr, &fb->colorTex);
if (FAILED(hr)) {
delete fb;
return nullptr;
}
hr = device_->CreateRenderTargetView(fb->colorTex.Get(), nullptr, &fb->colorRTView);
if (FAILED(hr)) {
delete fb;
return nullptr;
}
hr = device_->CreateShaderResourceView(fb->colorTex.Get(), nullptr, &fb->colorSRView);
if (FAILED(hr)) {
delete fb;
return nullptr;
}
if (desc.z_stencil) {
fb->depthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
D3D11_TEXTURE2D_DESC descDepth{};
descDepth.Width = desc.width;
descDepth.Height = desc.height;
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = DXGI_FORMAT_R24G8_TYPELESS; // so we can create an R24X8 view of it.
descDepth.SampleDesc.Count = 1;
descDepth.SampleDesc.Quality = 0;
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;
hr = device_->CreateTexture2D(&descDepth, nullptr, &fb->depthStencilTex);
if (FAILED(hr)) {
delete fb;
return nullptr;
}
D3D11_DEPTH_STENCIL_VIEW_DESC descDSV{};
descDSV.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
descDSV.Texture2D.MipSlice = 0;
hr = device_->CreateDepthStencilView(fb->depthStencilTex.Get(), &descDSV, &fb->depthStencilRTView);
if (FAILED(hr)) {
delete fb;
return nullptr;
}
D3D11_SHADER_RESOURCE_VIEW_DESC depthViewDesc{};
depthViewDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
depthViewDesc.Texture2D.MostDetailedMip = 0;
depthViewDesc.Texture2D.MipLevels = 1;
depthViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
hr = device_->CreateShaderResourceView(fb->depthStencilTex.Get(), &depthViewDesc, &fb->depthSRView);
if (FAILED(hr)) {
WARN_LOG(Log::G3D, "Failed to create SRV for depth buffer.");
fb->depthSRView = nullptr;
}
D3D11_SHADER_RESOURCE_VIEW_DESC depthStencilViewDesc{};
depthStencilViewDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
depthStencilViewDesc.Texture2D.MostDetailedMip = 0;
depthStencilViewDesc.Texture2D.MipLevels = 1;
depthStencilViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
hr = device_->CreateShaderResourceView(fb->depthStencilTex.Get(), &depthViewDesc, &fb->stencilSRView);
if (FAILED(hr)) {
WARN_LOG(Log::G3D, "Failed to create SRV for depth+stencil buffer.");
fb->depthSRView = nullptr;
}
}
return fb;
}
void D3D11DrawContext::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
// Collect the resource views from the textures.
ID3D11ShaderResourceView *views[MAX_BOUND_TEXTURES];
_assert_(start + count <= ARRAY_SIZE(views));
for (int i = 0; i < count; i++) {
D3D11Texture *tex = (D3D11Texture *)textures[i];
views[i] = tex ? tex->View() : nullptr;
}
context_->PSSetShaderResources(start, count, views);
}
void D3D11DrawContext::BindNativeTexture(int index, void *nativeTexture) {
// Collect the resource views from the textures.
ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)nativeTexture;
context_->PSSetShaderResources(index, 1, &view);
}
void D3D11DrawContext::BindSamplerStates(int start, int count, SamplerState **states) {
ID3D11SamplerState *samplers[MAX_BOUND_TEXTURES];
_assert_(start + count <= ARRAY_SIZE(samplers));
for (int i = 0; i < count; i++) {
D3D11SamplerState *samp = (D3D11SamplerState *)states[i];
samplers[i] = samp ? samp->ss.Get() : nullptr;
}
context_->PSSetSamplers(start, count, samplers);
}
void D3D11DrawContext::Clear(Aspect mask, uint32_t colorval, float depthVal, int stencilVal) {
if ((mask & Aspect::COLOR_BIT) && curRenderTargetView_) {
float colorRGBA[4];
Uint8x4ToFloat4(colorRGBA, colorval);
context_->ClearRenderTargetView(curRenderTargetView_.Get(), colorRGBA);
}
if ((mask & (Aspect::DEPTH_BIT | Aspect::STENCIL_BIT)) && curDepthStencilView_) {
UINT clearFlag = 0;
if (mask & Aspect::DEPTH_BIT)
clearFlag |= D3D11_CLEAR_DEPTH;
if (mask & Aspect::STENCIL_BIT)
clearFlag |= D3D11_CLEAR_STENCIL;
context_->ClearDepthStencilView(curDepthStencilView_.Get(), clearFlag, depthVal, stencilVal);
}
}
void D3D11DrawContext::BeginFrame(DebugFlags debugFlags) {
FrameTimeData &frameTimeData = frameTimeHistory_.Add(frameCount_);
frameTimeData.afterFenceWait = time_now_d();
frameTimeData.frameBegin = frameTimeData.afterFenceWait;
context_->OMSetRenderTargets(1, curRenderTargetView_.GetAddressOf(), curDepthStencilView_.Get());
if (curBlend_ != nullptr) {
context_->OMSetBlendState(curBlend_->bs.Get(), blendFactor_, 0xFFFFFFFF);
}
if (curDepthStencil_ != nullptr) {
ComPtr<ID3D11DepthStencilState> dss;
GetCachedDepthStencilState(curDepthStencil_, stencilWriteMask_, stencilCompareMask_, &dss);
context_->OMSetDepthStencilState(dss.Get(), stencilRef_);
}
if (curRaster_ != nullptr) {
context_->RSSetState(curRaster_->rs.Get());
}
context_->IASetInputLayout(curInputLayout_.Get());
context_->VSSetShader(curVS_.Get(), nullptr, 0);
context_->PSSetShader(curPS_.Get(), nullptr, 0);
if (curTopology_ != D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED) {
context_->IASetPrimitiveTopology(curTopology_);
}
if (curPipeline_ != nullptr) {
context_->IASetVertexBuffers(0, 1, nextVertexBuffer_.GetAddressOf(), &curPipeline_->input->stride, &nextVertexBufferOffset_);
context_->IASetIndexBuffer(nextIndexBuffer_.Get(), DXGI_FORMAT_R16_UINT, nextIndexBufferOffset_);
if (curPipeline_->dynamicUniforms) {
context_->VSSetConstantBuffers(0, 1, curPipeline_->dynamicUniforms.GetAddressOf());
context_->PSSetConstantBuffers(0, 1, curPipeline_->dynamicUniforms.GetAddressOf());
}
}
}
void D3D11DrawContext::CopyFramebufferImage(Framebuffer *srcfb, int level, int x, int y, int z, Framebuffer *dstfb, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, Aspect aspect, const char *tag) {
D3D11Framebuffer *src = (D3D11Framebuffer *)srcfb;
D3D11Framebuffer *dst = (D3D11Framebuffer *)dstfb;
ComPtr<ID3D11Texture2D> srcTex;
ComPtr<ID3D11Texture2D> dstTex;
switch (aspect) {
case Aspect::COLOR_BIT:
srcTex = src->colorTex;
dstTex = dst->colorTex;
break;
case Aspect::DEPTH_BIT:
srcTex = src->depthStencilTex;
dstTex = dst->depthStencilTex;
break;
case Aspect::NO_BIT:
case Aspect::STENCIL_BIT:
case Aspect::VIEW_BIT:
case Aspect::FORMAT_BIT:
break;
}
_assert_(srcTex && dstTex);
// TODO: Check for level too!
if (width == src->Width() && width == dst->Width() && height == src->Height() && height == dst->Height() && x == 0 && y == 0 && z == 0 && dstX == 0 && dstY == 0 && dstZ == 0) {
// Don't need to specify region. This might be faster, too.
context_->CopyResource(dstTex.Get(), srcTex.Get());
return;
}
if (aspect != Aspect::DEPTH_BIT) {
// Non-full copies are not supported for the depth channel.
// Note that we need to clip the source box.
if (x < 0) {
width += x; // note that x is negative
dstX -= x;
x = 0;
}
if (y < 0) {
height += y; // note that y is negative
dstY -= y;
y = 0;
}
if (x + width > src->Width()) {
width = src->Width() - x;
}
if (y + height > src->Height()) {
height = src->Height() - y;
}
D3D11_BOX srcBox{ (UINT)x, (UINT)y, (UINT)z, (UINT)(x + width), (UINT)(y + height), (UINT)(z + depth) };
context_->CopySubresourceRegion(dstTex.Get(), dstLevel, dstX, dstY, dstZ, srcTex.Get(), level, &srcBox);
}
}
bool D3D11DrawContext::BlitFramebuffer(Framebuffer *srcfb, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dstfb, int dstX1, int dstY1, int dstX2, int dstY2, Aspect aspects, FBBlitFilter filter, const char *tag) {
// Unfortunately D3D11 has no equivalent to this, gotta render a quad. Well, in some cases we can issue a copy instead.
Crash();
return false;
}
bool D3D11DrawContext::CopyFramebufferToMemory(Framebuffer *src, Aspect channelBits, int bx, int by, int bw, int bh, Draw::DataFormat destFormat, void *pixels, int pixelStride, ReadbackMode mode, const char *tag) {
D3D11Framebuffer *fb = (D3D11Framebuffer *)src;
if (fb) {
_assert_(fb->colorFormat == DXGI_FORMAT_R8G8B8A8_UNORM);
// TODO: Figure out where the badness really comes from.
if (bx + bw > fb->Width()) {
bw -= (bx + bw) - fb->Width();
}
if (by + bh > fb->Height()) {
bh -= (by + bh) - fb->Height();
}
}
if (bh <= 0 || bw <= 0)
return true;
bool useGlobalPacktex = (bx + bw <= 512 && by + bh <= 512) && channelBits == Aspect::COLOR_BIT;
ComPtr<ID3D11Texture2D> packTex;
if (!useGlobalPacktex) {
D3D11_TEXTURE2D_DESC packDesc{};
packDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
packDesc.BindFlags = 0;
packDesc.ArraySize = 1;
packDesc.MipLevels = 1;
packDesc.Usage = D3D11_USAGE_STAGING;
packDesc.SampleDesc.Count = 1;
// The copy below lands at bx, by, and the read starts there, so the texture has to reach that far.
packDesc.Width = bx + bw;
packDesc.Height = by + bh;
switch (channelBits) {
case Aspect::COLOR_BIT:
packDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // TODO: fb->colorFormat;
break;
case Aspect::DEPTH_BIT:
case Aspect::STENCIL_BIT:
if (!fb) {
// Not supported.
return false;
}
packDesc.Format = fb->depthStencilFormat;
// CopyResource below needs the whole size.
packDesc.Width = fb->Width();
packDesc.Height = fb->Height();
break;
default:
_assert_(false);
}
device_->CreateTexture2D(&packDesc, nullptr, &packTex);
} else {
switch (channelBits) {
case Aspect::DEPTH_BIT:
case Aspect::STENCIL_BIT:
if (!fb)
return false;
break;
default:
break;
}
packTex = packTexture_;
}
if (!packTex)
return false;
D3D11_BOX srcBox{ (UINT)bx, (UINT)by, 0, (UINT)(bx + bw), (UINT)(by + bh), 1 };
DataFormat srcFormat = DataFormat::UNDEFINED;
switch (channelBits) {
case Aspect::COLOR_BIT:
context_->CopySubresourceRegion(packTex.Get(), 0, bx, by, 0, fb ? fb->colorTex.Get() : bbRenderTargetTex_, 0, &srcBox);
srcFormat = DataFormat::R8G8B8A8_UNORM;
break;
case Aspect::DEPTH_BIT:
case Aspect::STENCIL_BIT:
// For depth/stencil buffers, we can't reliably copy subrectangles, so just copy the whole resource.
_assert_(fb); // Can't copy depth/stencil from backbuffer. Shouldn't happen thanks to checks above.
context_->CopyResource(packTex.Get(), fb->depthStencilTex.Get());
srcFormat = Draw::DataFormat::D24_S8;
break;
default:
_assert_(false);
break;
}
// Ideally, we'd round robin between two packTexture_, and simply use the other one. Though if the game
// does a once-off copy, that won't work at all.
// BIG GPU STALL
D3D11_MAPPED_SUBRESOURCE map;
HRESULT result = context_->Map(packTex.Get(), 0, D3D11_MAP_READ, 0, &map);
if (FAILED(result)) {
return false;
}
const size_t srcByteOffset = by * map.RowPitch + bx * DataFormatSizeInBytes(srcFormat);
const uint8_t *srcWithOffset = (const uint8_t *)map.pData + srcByteOffset;
switch ((Aspect)channelBits) {
case Aspect::COLOR_BIT:
// Pixel size always 4 here because we always request RGBA8888.
ConvertFromRGBA8888((uint8_t *)pixels, srcWithOffset, pixelStride, map.RowPitch / sizeof(uint32_t), bw, bh, destFormat);
break;
case Aspect::DEPTH_BIT:
if (srcFormat == destFormat) {
// Can just memcpy when it matches no matter the format!
uint8_t *dst = (uint8_t *)pixels;
const uint8_t *src = (const uint8_t *)srcWithOffset;
for (int y = 0; y < bh; ++y) {
memcpy(dst, src, bw * DataFormatSizeInBytes(srcFormat));
dst += pixelStride * DataFormatSizeInBytes(srcFormat);
src += map.RowPitch;
}
} else if (destFormat == DataFormat::D32F) {
ConvertToD32F((uint8_t *)pixels, srcWithOffset, pixelStride, map.RowPitch / sizeof(uint32_t), bw, bh, srcFormat);
} else if (destFormat == DataFormat::D16) {
ConvertToD16((uint8_t *)pixels, srcWithOffset, pixelStride, map.RowPitch / sizeof(uint32_t), bw, bh, srcFormat);
} else {
_assert_(false);
}
break;
case Aspect::STENCIL_BIT:
if (srcFormat == destFormat) {
// Can just memcpy when it matches no matter the format!
uint8_t *dst = (uint8_t *)pixels;
const uint8_t *src = (const uint8_t *)srcWithOffset;
for (int y = 0; y < bh; ++y) {
memcpy(dst, src, bw * DataFormatSizeInBytes(srcFormat));
dst += pixelStride * DataFormatSizeInBytes(srcFormat);
src += map.RowPitch;
}
} else if (destFormat == DataFormat::S8) {
for (int y = 0; y < bh; y++) {
uint8_t *destStencil = (uint8_t *)pixels + y * pixelStride;
const uint32_t *src = (const uint32_t *)(srcWithOffset + map.RowPitch * y);
for (int x = 0; x < bw; x++) {
destStencil[x] = src[x] >> 24;
}
}
} else {
_assert_(false);
}
break;
case Aspect::NO_BIT:
case Aspect::VIEW_BIT:
case Aspect::FORMAT_BIT:
break;
}
context_->Unmap(packTex.Get(), 0);
return true;
}
void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) {
// TODO: deviceContext1 can actually discard.
if (fbo) {
D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo;
if (curRenderTargetView_ == fb->colorRTView && curDepthStencilView_ == fb->depthStencilRTView) {
// No need to switch, but let's fallthrough to clear!
} else {
// It's not uncommon that the first slot happens to have the new render target bound as a texture,
// so unbind to make the validation layers happy.
ID3D11ShaderResourceView *empty[1] = {};
context_->PSSetShaderResources(0, ARRAY_SIZE(empty), empty);
context_->OMSetRenderTargets(1, fb->colorRTView.GetAddressOf(), fb->depthStencilRTView.Get());
curRenderTargetView_ = fb->colorRTView;
curDepthStencilView_ = fb->depthStencilRTView;
curRTWidth_ = fb->Width();
curRTHeight_ = fb->Height();
}
curRenderTarget_ = fb;
} else {
if (curRenderTargetView_.Get() == bbRenderTargetView_ && curDepthStencilView_ == bbDepthStencilView_) {
// No need to switch, but let's fallthrough to clear!
} else {
context_->OMSetRenderTargets(1, &bbRenderTargetView_, bbDepthStencilView_.Get());
curRenderTargetView_ = bbRenderTargetView_;
curDepthStencilView_ = bbDepthStencilView_;
curRTWidth_ = bbWidth_;
curRTHeight_ = bbHeight_;
}
curRenderTarget_ = nullptr;
}
if (rp.color == RPAction::CLEAR && curRenderTargetView_) {
float cv[4]{};
if (rp.clearColor)
Uint8x4ToFloat4(cv, rp.clearColor);
context_->ClearRenderTargetView(curRenderTargetView_.Get(), cv);
}
int mask = 0;
if (rp.depth == RPAction::CLEAR) {
mask |= D3D11_CLEAR_DEPTH;
}
if (rp.stencil == RPAction::CLEAR) {
mask |= D3D11_CLEAR_STENCIL;
}
if (mask && curDepthStencilView_) {
context_->ClearDepthStencilView(curDepthStencilView_.Get(), mask, rp.clearDepth, rp.clearStencil);
}
if (invalidationCallback_) {
invalidationCallback_(InvalidationCallbackFlags::RENDER_PASS_STATE);
}
}
void D3D11DrawContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, Aspect channelBit, int layer) {
_dbg_assert_(binding < MAX_BOUND_TEXTURES);
_dbg_assert_(layer == ALL_LAYERS || layer == 0); // No multiple layer support on D3D
D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo;
switch (channelBit) {
case Aspect::COLOR_BIT:
context_->PSSetShaderResources(binding, 1, fb->colorSRView.GetAddressOf());
break;
case Aspect::DEPTH_BIT:
if (fb->depthSRView) {
context_->PSSetShaderResources(binding, 1, fb->depthSRView.GetAddressOf());
}
break;
default:
break;
}
}
uint64_t D3D11DrawContext::GetNativeObject(NativeObject obj, void *srcObject) {
switch (obj) {
case NativeObject::DEVICE:
return (uint64_t)(uintptr_t)device_.Get();
case NativeObject::CONTEXT:
return (uint64_t)(uintptr_t)context_.Get();
case NativeObject::DEVICE_EX:
return (uint64_t)(uintptr_t)device1_.Get();
case NativeObject::CONTEXT_EX:
return (uint64_t)(uintptr_t)context1_.Get();
case NativeObject::BACKBUFFER_COLOR_TEX:
return (uint64_t)(uintptr_t)bbRenderTargetTex_;
case NativeObject::BACKBUFFER_DEPTH_TEX:
return (uint64_t)(uintptr_t)bbDepthStencilTex_.Get();
case NativeObject::BACKBUFFER_COLOR_VIEW:
return (uint64_t)(uintptr_t)bbRenderTargetView_;
case NativeObject::BACKBUFFER_DEPTH_VIEW:
return (uint64_t)(uintptr_t)bbDepthStencilView_.Get();
case NativeObject::FEATURE_LEVEL:
return (uint64_t)(uintptr_t)featureLevel_;
case NativeObject::TEXTURE_VIEW:
return (uint64_t)(((D3D11Texture *)srcObject)->View());
default:
return 0;
}
}
void D3D11DrawContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) {
D3D11Framebuffer *fb = (D3D11Framebuffer *)fbo;
if (fb) {
*w = fb->Width();
*h = fb->Height();
} else {
*w = bbWidth_;
*h = bbHeight_;
}
}
DrawContext *T3DCreateD3D11Context(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> context, ComPtr<ID3D11Device1> device1, ComPtr<ID3D11DeviceContext1> context1, ComPtr<IDXGISwapChain> swapChain, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, const std::vector<std::string> &adapterNames, int maxInflightFrames) {
return new D3D11DrawContext(device, context, device1, context1, swapChain, featureLevel, hWnd, adapterNames, maxInflightFrames);
}
} // namespace Draw