Files
parallel-dc/core/rend/dx12/oit/dx12_oitrenderer.cpp
T
libretroadmin b60d97627a Direct3D 12: draw the frame from where it is parsed
Where the adapter's memory is the processor's, kept coherent with its
cache (CacheCoherentUMA), the frame's vertices, indices and modifier
volumes' triangles are parsed straight into a buffer of a custom heap
with write-back pages, mapped for good, in four slices
(dx12/parse_arena_d3d12.h, C89): no copy into the upload ring. The
sorted passes' indices go after the frame's there. A slice is taken when
the context's fence has passed the value of the submission that last
drew from it, polled; with none free, or another adapter, the frame is
copied as ever. Render() is the base's, final, around RenderFrame().

d3d_hdr.c's D3DH_NOT_UMA has the adapter say otherwise and D3DH_EVERY
prints every frame's sum; d3d_parse_arena.sh checks every render drawn
from where it was parsed and every frame the one of the lists copied,
under wine.
2026-11-02 17:04:49 +01:00

1480 lines
58 KiB
C++

#define PVR_REGS_FOR_RENDERER // see hw/pvr/pvr_regs.h
/*
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
/* The per-pixel renderer: the translucent polygons' pixels are put in
* lists, sorted by depth and blended at the end, as the Direct3D 11
* per-pixel renderer does it (dx11/oit/dx11_oitrenderer.cpp), step for
* step:
*
* - the opaque and punch-through polygons' depth, and the modifier
* volumes into the stencil;
* - their colour, where their depth is the one left, darkened where the
* stencil says they are in a volume;
* - the translucent polygons' pixels into the lists (or, in a list that
* is not to be sorted, drawn as they come);
* - the translucent modifier volumes, marking the pixels in the lists;
* - the lists put together over the colour, into the picture - or, with
* a render pass after this one, into a picture the next pass draws on.
*
* In bands when a frame has more translucent pixels than the lists hold
* (ChooseBands()), the counts read back without waiting through a ring of
* them in a readback buffer, a frame's counts read once the fence says its
* list is done.
*
* Direct3D 12's own: the lists, their heads and the counter are a table of
* views the root signature has for them (DX12Context::OitFirst); the depth
* and the stencil are read through views of the depth buffer's planes in
* that table, each plane in the state its pass needs; what the shaders do
* to the lists is ordered by barriers between the passes, which Direct3D
* 11 did by itself; the translucent polygons' parameters go to the GPU in
* the upload ring, as a root descriptor. The rest is the per-triangle
* renderer's: the textures, renders to textures, the screen's pictures,
* the last picture, the batching of the draws.
*
* SSAA (dx12_renderer.h), as the Vulkan per-pixel renderer has it: the
* screen's opaque and punch-through polygons are drawn into two pictures of
* samples (the base renderer's SsaaColor), with the samples' depth and
* stencil, which the shaders read through the table of views for them
* (DX12Context::OitSsaaFirst); a translucent fragment of a sorted list is
* put in the lists once a pixel, with the samples it covers and is in
* front of the opaque depth at; the last render pass's lists are sorted
* once a pixel, blended at each sample onto that sample's opaque colour,
* and the samples resolved by the shared rule into the screen's picture,
* in one pass; one that is not the last puts each sample together into the
* other picture of samples, for the next to draw over. Translucent
* modifier volumes count where they cover a pixel's first sample. */
#include "../dx12_renderer.h"
#include "../dx12_quad.h"
#include "../dx12_compute.h"
#include "rend/oit_size.h"
#include "rend/rend_method.h"
#include "rend/ssaa_rate.h"
#include "hw/pvr/ta.h"
#include <algorithm>
/* (the most a resource can be: a quarter of the card's memory, but at
* least 128 MB and at most 2 GB; mingw-w64's header has not the names) */
#ifndef D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM
#define D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM 128
#endif
#ifndef D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_C_TERM
#define D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_C_TERM 2048
#endif
class DX12OITRenderer final : public DX12Renderer
{
public:
bool Init() override
{
/* (Rendering Method: Compute draws into the screen's pictures, which
* it writes through unordered access views) */
storagePictures = rend_compute_wanted;
if (!DX12Renderer::Init())
return false;
if (!MakeBuffers(PlannedSize(screen_width, screen_height)))
{
WARN_LOG(RENDERER, "DX12 per-pixel renderer initialization failed");
Term();
return false;
}
/* The translucent polygons' numbers past the root constant's, an
* instance's: 0 for every draw but a frame decoded on the GPU's
* translucent ones (DX12TaDraw::instances) */
u32 *zero = nullptr;
if (!MakeBuffer(noInstance, 256, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ)
|| FAILED(noInstance.resource->Map(0, nullptr, (void **)&zero)))
{
WARN_LOG(RENDERER, "DX12 per-pixel renderer initialization failed");
Term();
return false;
}
memset(zero, 0, 256);
noInstance.resource->Unmap(0, nullptr);
countHead = 0;
for (u32 i = 0; i < COUNT_SLOTS; i++)
{
countWords[i] = 0;
countFence[i] = 0;
}
oitBands[0] = oitBands[1] = 1;
oitRoomyFrames[0] = oitRoomyFrames[1] = 0;
band = {};
InitCompute();
return true;
}
void Resize(int w, int h) override
{
DX12Renderer::Resize(w, h);
InitCompute();
}
bool RenderFrame() override;
void Term() override
{
compute.Term();
if (context != nullptr)
{
for (DX12Image& picture : pictures)
DropImage(picture);
picturesWidth = picturesHeight = 0;
DropBuffer(pointers);
pointersWidth = pointersHeight = 0;
DropBuffer(pixels);
DropBuffer(counter);
DropBuffer(noInstance);
if (counts.resource && countsMapped != nullptr)
counts.resource->Unmap(0, nullptr);
countsMapped = nullptr;
DropBuffer(counts);
bufferSize = room = pixelBufferRoom = 0;
depthViewed = nullptr;
clearHeap.Term();
DropSums();
}
DX12Renderer::Term();
}
protected:
bool BindsScreen() const override { return false; }
/* GPU-side TA processing: the translucent draw records number each
* strip (TA_GPU_INSTANCE), which picks its number from the instances'
* buffer; no sorting here */
u32 TaFlags() const override { return TA_GPU_DRAW | TA_GPU_PROVOKE | TA_GPU_INSTANCE; }
bool TaSorts() const override { return false; }
// SSAA: two pictures of samples, and the lists first
u32 SsaaColorImages() const override { return 2; }
u32 SsaaFixedKib(u32 width, u32 height) const override { return (u32)std::min<u64>(PlannedSize(width, height) >> 10, 0xffffffffu); }
void PreparePipelines() override;
void DrawFrame() override;
D3D12_RECT CutScissor(const D3D12_RECT& rect) const override
{
if (band.bottom == 0)
return rect;
D3D12_RECT cut = rect;
cut.top = std::max(rect.top, band.top);
cut.bottom = std::max(std::min(rect.bottom, band.bottom), cut.top);
return cut;
}
private:
/* ---- Rendering Method: Compute (dx12_compute.h) ----
* The frames it can draw are drawn into the screen's pictures by it, and
* the others below; made with SSAA's samples, when the renderer starts
* and when the screen changes size */
DX12ComputeDrawer compute;
void InitCompute()
{
if (rend_compute_wanted && device != nullptr)
{
compute.SetGpuDraw(&taDecoder);
// (for the temporal upscaler, with the walks that store the depth)
compute.Init((u32)std::max(screen_width, 1), (u32)std::max(screen_height, 1), ssaaSamples, upscaler_active() && ssaaSamples == 1);
}
else
compute.Term();
}
/* The frame in pvrrc drawn by it into the screen's picture, and shown;
* false if it is not (nothing recorded then) */
bool RenderCompute();
// ...a render to a texture, into its texture
bool RenderComputeRtt();
/* The frame's words by the per-pixel renderer's own rules */
struct ComputeRules : public ComputeFrame
{
DX12OITRenderer *renderer = nullptr;
const xform& Begin() override
{
renderer->matrices.CalcMatrices(&pvrrc);
return renderer->matrices.GetViewportMatrix();
}
void BaseScissor(int& x, int& y, int& width, int& height) override
{
renderer->SetBaseScissor();
const D3D12_RECT& r = renderer->baseScissor;
x = r.left;
y = r.top;
width = r.right - r.left;
height = r.bottom - r.top;
}
void ProvokingVertices() override { renderer->SetProvokingVertices(); }
};
ComputeRules computeRules;
enum { COUNT_SLOTS = 4, COUNT_WORDS = 256 };
struct Buffer
{
ComPtr<ID3D12Resource> resource;
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COMMON;
};
/* For a screen of @width x @height: rend/oit_size.h, and no more than
* a resource can be */
u64 PlannedSize(int width, int height) const
{
return oit_buffer_bytes((u32)std::max(width, 0), (u32)std::max(height, 0),
context->GetVramBudget().available_kib(), (u64)D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_C_TERM * 1024u * 1024u);
}
bool MakeBuffers(u64 wanted);
bool MakeBuffer(Buffer& buffer, u64 bytes, D3D12_HEAP_TYPE heap, D3D12_RESOURCE_FLAGS flags, D3D12_RESOURCE_STATES state);
void DropBuffer(Buffer& buffer);
bool MakePictures(u32 width, u32 height);
bool UploadTrPolyParams();
/* The counter back to 0: the lists are empty, the buffer filled from its start */
void ResetCount();
/* The counter, as the draws before left it, copied into @word of @slot
* of the counts: nothing waits for it */
void CopyCount(u32 slot, u32 word);
/* The counts @slot holds, read if the GPU is done with the list that
* copied them, and the largest of them in @largest: false when it is
* not done, or when nothing was copied there. Never waits. */
bool ReadCount(u32 slot, u32& largest);
bool HasCount(u32 slot) const { return slot < COUNT_SLOTS && countWords[slot] != 0; }
u32 ChooseBands(bool rtt);
/* What the shaders did to the lists, done before what comes next */
void ListsBarrier()
{
context->UavBarrier(pixels.resource);
context->UavBarrier(pointers.resource);
context->UavBarrier(counter.resource);
}
void DrawPoly(u32 listType, bool autosort, u32 pass, const PolyParam& poly, u32 first, u32 count);
void DrawList(u32 listType, bool autosort, u32 pass, const List<PolyParam>& polys, u32 first, u32 last);
void DrawModifierVolumes(bool translucent, int first, int count, const ModifierVolumeParam *params);
// (@sums: and the samples' channel sums stored, into sums)
void Resolve(u32 targetRtv, u32 opaqueSrv, bool intoSamples, bool sums = false);
/* With the HDR output and SSAA: each pixel's channel sums, which the
* screen's resolve stores (DX12_RATE_PIXEL_SUMS) for the encode
* (DX12Context::EncodeHdr()); made for @width x @height when they are
* not. False when they cannot be. */
bool MakeSums(u32 width, u32 height);
void DropSums();
DX12Image *HdrSums() override { return sumsStored ? &sums : nullptr; }
DX12Image sums;
bool sumsStored = false; // by the last screen frame
void ClearStencil()
{
DepthStates(D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_DEPTH_WRITE);
context->Cmd()->ClearDepthStencilView(context->Dsv().Cpu(depth.rtv), D3D12_CLEAR_FLAG_STENCIL, 0.f, 0, 0, nullptr);
}
// the lists (OIT_FRAGMENT_BYTES each), their heads (R32_UINT, a picture's size), the counter (a word)
Buffer pixels;
Buffer pointers;
u32 pointersWidth = 0, pointersHeight = 0;
Buffer counter;
u64 bufferSize = 0;
u32 room = 0;
// the counts read back: COUNT_SLOTS of COUNT_WORDS, mapped for good
Buffer counts;
const u32 *countsMapped = nullptr;
u32 countWords[COUNT_SLOTS] = {};
u64 countFence[COUNT_SLOTS] = {}; // the fence value of the list that copied them
/* What the opaque and punch-through polygons are drawn into, two of
* them: with several render passes, each draws on what the last one
* put together */
DX12Image pictures[2];
u32 picturesWidth = 0;
u32 picturesHeight = 0;
// the depth buffer the table's depth and stencil views are of
ID3D12Resource *depthViewed = nullptr;
/* A view of the lists' heads where the CPU has it, for them to be
* cleared with (SSAA, a render pass that is not the last: Resolve()) */
DX12DescriptorHeap clearHeap;
/* The translucent polygons' parameters, in the ring */
Buffer noInstance; // a zero, the instances' buffer of a frame parsed on the CPU
/* A frame drawn in horizontal bands (DrawFrame()): the band being
* drawn, in pixels of the target - from its top to its bottom, which is
* 0 when the frame is drawn whole */
struct { LONG top, bottom; } band = {};
/* The counts of the frames in flight: the slot the frame being drawn
* copies into, and the bands each slot's frame was drawn in and whether
* it was a render to a texture */
u32 countHead = 0;
u32 countBands[COUNT_SLOTS] = {};
bool countRtt[COUNT_SLOTS] = {};
// how many bands frames are drawn in: the screen's [0], renders to a texture [1]
u32 oitBands[2] = { 1, 1 };
u32 oitRoomyFrames[2] = {};
};
/* ---- The buffers ---- */
bool DX12OITRenderer::MakeBuffer(Buffer& buffer, u64 bytes, D3D12_HEAP_TYPE heapType, D3D12_RESOURCE_FLAGS flags, D3D12_RESOURCE_STATES state)
{
DropBuffer(buffer);
D3D12_HEAP_PROPERTIES heap{};
heap.Type = heapType;
D3D12_RESOURCE_DESC desc{};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = bytes;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
desc.Flags = flags;
if (FAILED(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
__uuidof(ID3D12Resource), (void **)buffer.resource.put())))
{
buffer.resource.reset();
return false;
}
buffer.state = state;
return true;
}
void DX12OITRenderer::DropBuffer(Buffer& buffer)
{
if (buffer.resource)
{
buffer.resource->AddRef();
context->Release(buffer.resource.get());
buffer.resource.reset();
}
buffer.state = D3D12_RESOURCE_STATE_COMMON;
}
/* The lists: one large buffer (@wanted bytes, or as large as the card will
* make it), the counter, the counts' readback ring; their views into the
* table */
bool DX12OITRenderer::MakeBuffers(u64 wanted)
{
/* (the most a resource can be is a quarter of the card's memory, but
* at least 128 MB and at most 2 GB: it is asked for no larger than
* that, and halved until the card makes it, a whole number of
* pixels each time) */
const u64 largest = (u64)D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_C_TERM * 1024u * 1024u;
const u64 smallest = (u64)D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
u64 bytes = std::min<u64>(wanted, largest) & ~(u64)(OIT_FRAGMENT_BYTES - 1);
bool ok = MakeBuffer(pixels, bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
while (!ok && bytes > smallest)
{
bytes = std::max((bytes / 2) & ~(u64)(OIT_FRAGMENT_BYTES - 1), smallest);
ok = MakeBuffer(pixels, bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}
if (!ok)
{
WARN_LOG(RENDERER, "DX12: pixel buffer creation failed");
return false;
}
bufferSize = wanted;
room = pixelBufferRoom = (u32)(bytes / OIT_FRAGMENT_BYTES);
if (bytes < wanted)
NOTICE_LOG(RENDERER, "DX12: pixel buffer of %u MB", (u32)(bytes / 1024 / 1024));
D3D12_UNORDERED_ACCESS_VIEW_DESC uav{};
uav.Format = DXGI_FORMAT_UNKNOWN;
uav.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
uav.Buffer.NumElements = room;
uav.Buffer.StructureByteStride = OIT_FRAGMENT_BYTES;
device->CreateUnorderedAccessView(pixels.resource, nullptr, &uav, context->Srv().Cpu(DX12Context::OitFirst + 0));
device->CreateUnorderedAccessView(pixels.resource, nullptr, &uav, context->Srv().Cpu(DX12Context::OitSsaaFirst + 0));
// the counter: a word, counted up by the shaders
if (!MakeBuffer(counter, 256, D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_UNORDERED_ACCESS))
{
WARN_LOG(RENDERER, "DX12: pixel counter creation failed");
return false;
}
D3D12_UNORDERED_ACCESS_VIEW_DESC counterUav{};
counterUav.Format = DXGI_FORMAT_R32_TYPELESS;
counterUav.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
counterUav.Buffer.NumElements = 4;
counterUav.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
device->CreateUnorderedAccessView(counter.resource, nullptr, &counterUav, context->Srv().Cpu(DX12Context::OitFirst + 2));
device->CreateUnorderedAccessView(counter.resource, nullptr, &counterUav, context->Srv().Cpu(DX12Context::OitSsaaFirst + 2));
// the counts read back, mapped once
if (!counts.resource)
{
if (!MakeBuffer(counts, COUNT_SLOTS * COUNT_WORDS * sizeof(u32), D3D12_HEAP_TYPE_READBACK, D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST))
{
WARN_LOG(RENDERER, "DX12: pixel count readback buffer creation failed");
return false;
}
const D3D12_RANGE whole = { 0, COUNT_SLOTS * COUNT_WORDS * sizeof(u32) };
void *p = nullptr;
if (FAILED(counts.resource->Map(0, &whole, &p)))
return false;
countsMapped = (const u32 *)p;
}
return true;
}
bool DX12OITRenderer::MakePictures(u32 width, u32 height)
{
/* (made again for another size of screen; a render to a texture is not
* one, and a frame that wants more is drawn in bands) */
const u64 wanted = PlannedSize(screen_width, screen_height);
if (wanted != bufferSize && !MakeBuffers(wanted))
return false;
// the lists' heads, a picture of at least this size: none of them a list (EOL)
if (!pointers.resource || width > pointersWidth || height > pointersHeight)
{
width = std::max(width, pointersWidth);
height = std::max(height, pointersHeight);
DropBuffer(pointers);
pointersWidth = pointersHeight = 0;
D3D12_HEAP_PROPERTIES heap{};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC desc{};
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Width = width;
desc.Height = height;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = DXGI_FORMAT_R32_UINT;
desc.SampleDesc.Count = 1;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
pointers.state = D3D12_RESOURCE_STATE_COPY_DEST;
if (FAILED(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, pointers.state, nullptr,
__uuidof(ID3D12Resource), (void **)pointers.resource.put())))
{
WARN_LOG(RENDERER, "DX12: per-pixel list heads creation failed (%d x %d)", width, height);
pointers.resource.reset();
return false;
}
// filled with EOL through the ring
const u32 pitch = (width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1);
DX12Upload upload;
if (!context->Upload(pitch * height, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT, upload))
{
WARN_LOG(RENDERER, "DX12: the upload ring is too small for the per-pixel list heads (%u KB)", pitch * height / 1024);
DropBuffer(pointers);
return false;
}
memset(upload.cpu, 0xff, (size_t)pitch * height);
D3D12_TEXTURE_COPY_LOCATION dst{};
dst.pResource = pointers.resource;
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
D3D12_TEXTURE_COPY_LOCATION src{};
src.pResource = upload.buffer;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint.Offset = upload.offset;
src.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R32_UINT;
src.PlacedFootprint.Footprint.Width = width;
src.PlacedFootprint.Footprint.Height = height;
src.PlacedFootprint.Footprint.Depth = 1;
src.PlacedFootprint.Footprint.RowPitch = pitch;
context->Cmd()->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
context->Transition(pointers.resource, pointers.state, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
D3D12_UNORDERED_ACCESS_VIEW_DESC uav{};
uav.Format = DXGI_FORMAT_R32_UINT;
uav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
device->CreateUnorderedAccessView(pointers.resource, nullptr, &uav, context->Srv().Cpu(DX12Context::OitFirst + 1));
device->CreateUnorderedAccessView(pointers.resource, nullptr, &uav, context->Srv().Cpu(DX12Context::OitSsaaFirst + 1));
if (clearHeap.Heap() != nullptr || clearHeap.Init(device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1, false))
device->CreateUnorderedAccessView(pointers.resource, nullptr, &uav, clearHeap.Cpu(0));
pointersWidth = width;
pointersHeight = height;
}
/* (with SSAA the screen is drawn into the pictures of samples: these
* are for the renders to textures) */
if (!SsaaActive() && (!pictures[0].resource || width > picturesWidth || height > picturesHeight))
{
width = std::max(width, picturesWidth);
height = std::max(height, picturesHeight);
picturesWidth = picturesHeight = 0;
for (DX12Image& picture : pictures)
{
DropImage(picture);
picture.state = D3D12_RESOURCE_STATE_RENDER_TARGET;
picture.width = width;
picture.height = height;
if (!dx12_make_texture(device, width, height, 1, DX12FrameFormat, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, picture.state, picture.resource))
{
WARN_LOG(RENDERER, "DX12: per-pixel renderer picture creation failed (%d x %d)", width, height);
return false;
}
picture.srv = context->Srv().Alloc();
picture.rtv = context->Rtv().Alloc();
if (picture.srv == ~0u || picture.rtv == ~0u)
{
DropImage(picture);
return false;
}
context->MakeSrv(picture.resource, DX12FrameFormat, 1, picture.srv);
device->CreateRenderTargetView(picture.resource, nullptr, context->Rtv().Cpu(picture.rtv));
}
picturesWidth = width;
picturesHeight = height;
}
if (!EnsureDepth(SsaaActive() ? targetWidth : width, SsaaActive() ? targetHeight : height))
return false;
// the shaders' views of the depth buffer's planes, when it is another (SSAA's have their own)
if (!SsaaActive() && depthViewed != depth.resource.get())
{
D3D12_SHADER_RESOURCE_VIEW_DESC view{};
view.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
view.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
view.Texture2D.MipLevels = 1;
view.Format = DXGI_FORMAT_X32_TYPELESS_G8X24_UINT;
view.Texture2D.PlaneSlice = 1;
device->CreateShaderResourceView(depth.resource, &view, context->Srv().Cpu(DX12Context::OitFirst + 3));
view.Format = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
view.Texture2D.PlaneSlice = 0;
device->CreateShaderResourceView(depth.resource, &view, context->Srv().Cpu(DX12Context::OitFirst + 4));
depthViewed = depth.resource;
}
return true;
}
/* What the shaders that put the lists together need to know of each
* translucent polygon: its TSP, depth function and mask, whether it takes
* shadows, and its second volume's TSP */
bool DX12OITRenderer::UploadTrPolyParams()
{
/* (a frame decoded on the GPU: its words are the decode's, and its
* strips' numbers the instances' buffer's) */
if (pvrrc.gpu)
{
const DX12TaDraw& gpu = taDecoder.Draw();
context->Cmd()->SetGraphicsRootShaderResourceView(6, gpu.trParams);
context->Cmd()->IASetVertexBuffers(1, 1, &gpu.instances);
return true;
}
const D3D12_VERTEX_BUFFER_VIEW none = { noInstance.resource->GetGPUVirtualAddress(), 256, 4 };
context->Cmd()->IASetVertexBuffers(1, 1, &none);
const int count = pvrrc.global_param_tr.used();
const u32 size = (u32)(std::max(count, 1) * 2 * sizeof(u32));
DX12Upload upload;
if (!context->Upload(size, 16, upload))
return false;
/* (written into the ring, in order: a pair of words a polygon, or one
* of zeros for none) */
u32 *words = (u32 *)upload.cpu;
if (count == 0)
words[0] = words[1] = 0;
const PolyParam *pp = pvrrc.global_param_tr.head();
for (int i = 0; i < count; i++, pp++, words += 2)
{
words[0] = (pp->tsp.full & 0xffff00c0) | ((pp->isp.full >> 16) & 0xe400) | ((pp->pcw.full >> 7) & 1);
words[1] = pp->tsp1.full;
}
context->Cmd()->SetGraphicsRootShaderResourceView(6, upload.gpu);
return true;
}
/* ---- The counts ---- */
void DX12OITRenderer::ResetCount()
{
DX12Upload zero;
if (!context->Upload(4, 4, zero))
return;
memset(zero.cpu, 0, 4);
context->Transition(counter.resource, counter.state, D3D12_RESOURCE_STATE_COPY_DEST);
context->Cmd()->CopyBufferRegion(counter.resource, 0, zero.buffer, zero.offset, 4);
context->Transition(counter.resource, counter.state, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
}
void DX12OITRenderer::CopyCount(u32 slot, u32 word)
{
if (slot >= COUNT_SLOTS || word >= COUNT_WORDS || !counts.resource)
return;
context->Transition(counter.resource, counter.state, D3D12_RESOURCE_STATE_COPY_SOURCE);
context->Cmd()->CopyBufferRegion(counts.resource, ((u64)slot * COUNT_WORDS + word) * sizeof(u32), counter.resource, 0, 4);
context->Transition(counter.resource, counter.state, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
if (word >= countWords[slot])
countWords[slot] = word + 1;
// (the list being recorded is the next one submitted)
countFence[slot] = context->LastSubmitted() + 1;
}
bool DX12OITRenderer::ReadCount(u32 slot, u32& largest)
{
if (slot >= COUNT_SLOTS || countWords[slot] == 0 || countsMapped == nullptr)
return false;
if (context->Completed() < countFence[slot])
return false;
const u32 *words = countsMapped + (size_t)slot * COUNT_WORDS;
largest = 0;
for (u32 i = 0; i < countWords[slot]; i++)
largest = std::max(largest, words[i]);
countWords[slot] = 0;
return true;
}
/* The bands this frame is drawn in, from the counts of the oldest frame
* whose list the GPU is done with: never this frame's, which nothing
* waits for. More at once when that one had pixels that found no room;
* fewer when a second of frames in a row would have fitted in fewer. Each
* band is taken to need what the fullest one did, with a quarter more
* room. The screen's frames and renders to a texture are counted apart. */
u32 DX12OITRenderer::ChooseBands(bool rtt)
{
constexpr u32 MaxBands = 64;
const u32 oldest = (countHead + 1) % COUNT_SLOTS; // when all are in flight
for (u32 k = 0; k < COUNT_SLOTS; k++)
{
const u32 slot = (oldest + k) % COUNT_SLOTS;
if (!HasCount(slot))
continue;
u32 fragments;
if (!ReadCount(slot, fragments))
// (still being drawn: the ones after it are younger)
break;
const u32 drawnIn = std::max<u32>(std::min<u32>(countBands[slot], MaxBands), 1);
const u64 roomNow = std::max<u64>(room, 1);
const u64 frame = (u64)fragments * drawnIn;
const u32 wanted = (u32)std::max<u64>(std::min<u64>((frame + frame / 4 + roomNow - 1) / roomNow, MaxBands), 1);
u32& bands = oitBands[countRtt[slot] ? 1 : 0];
u32& roomy = oitRoomyFrames[countRtt[slot] ? 1 : 0];
const u32 was = bands;
if (fragments > roomNow)
{
bands = std::min<u32>(std::max({ bands, wanted, drawnIn + 1 }), MaxBands);
roomy = 0;
}
else if (wanted < bands)
{
if (++roomy >= 60)
{
bands = wanted;
roomy = 0;
}
}
else
roomy = 0;
if (bands != was)
NOTICE_LOG(RENDERER, "DX12 per-pixel renderer: %u translucent pixels in a band of %u%s, room for %u: %u band%s",
fragments, drawnIn, countRtt[slot] ? " (a render to a texture)" : "", (u32)roomNow, bands, bands > 1 ? "s" : "");
break;
}
return oitBands[rtt ? 1 : 0];
}
/* ---- The pipelines ---- */
/* The ones the passes draw with, made before a game draws (the
* per-triangle renderer's are made by the context): the depth pass's with
* every depth mode, the colour pass's (equal), the lists' (greater or
* equal, nothing written), the unsorted translucent ones, the lists put
* together, the translucent volumes */
void DX12OITRenderer::PreparePipelines()
{
static const u8 blends[][2] = { { 4, 5 }, { 1, 1 }, { 1, 0 }, { 4, 1 }, { 0, 1 }, { 1, 5 } };
DX12Pipelines& pipelines = context->Pipelines();
DX12PsoKey k{};
for (u32 g = 0; g < 2; g++)
for (u32 cull = 0; cull < 3; cull++)
{
k = DX12PsoKey{};
k.kind = DX12_PSO_OIT;
k.gouraud = g != 0;
k.cull = cull;
k.depthBound = true;
k.depthTest = true;
// the depth pass: opaque, every mode; punch-through, blended, greater or equal
k.pass = DX12_PASS_DEPTH;
k.stencil = DX12Stencil::Shadow;
for (u32 func = 1; func < 8; func++)
for (u32 w = 0; w < 2; w++)
{
k.depthFunc = func;
k.depthWrite = w != 0;
pipelines.Prepare(k);
}
k.blend = true;
k.writeColor = true;
k.depthFunc = 6;
k.depthWrite = true;
for (const u8 *b : blends)
{
k.src = b[0];
k.dst = b[1];
pipelines.Prepare(k);
}
// the colour pass: where the depth is the one left; the unsorted translucent ones written
k.pass = DX12_PASS_COLOR;
k.stencil = DX12Stencil::None;
k.depthWrite = false;
k.depthFunc = 2;
for (const u8 *b : blends)
{
k.src = b[0];
k.dst = b[1];
pipelines.Prepare(k);
k.depthFunc = 6;
k.depthWrite = true;
pipelines.Prepare(k);
k.depthFunc = 7;
pipelines.Prepare(k);
k.depthWrite = false;
k.depthFunc = 2;
}
// the lists: nothing written but the lists
k.pass = DX12_PASS_OIT;
k.blend = false;
// (no blend, no instructions: as DrawPoly() keys them, not the last blend's above)
k.src = k.dst = 0;
k.writeColor = false;
k.depthFunc = 6;
k.depthWrite = false;
pipelines.Prepare(k);
// the translucent polygons' depth, for a pass after
k.pass = DX12_PASS_DEPTH;
k.depthWrite = true;
pipelines.Prepare(k);
}
k = DX12PsoKey{};
k.kind = DX12_PSO_OIT_FINAL;
k.writeColor = true;
k.depthFunc = 7;
pipelines.Prepare(k);
k = DX12PsoKey{};
k.kind = DX12_PSO_OIT_MODVOL;
k.depthFunc = 7;
for (u32 cull = 0; cull < 3; cull++)
{
k.cull = cull;
pipelines.Prepare(k);
}
}
/* ---- The draws ---- */
void DX12OITRenderer::DrawPoly(u32 listType, bool autosort, u32 pass, const PolyParam& poly, u32 first, u32 count)
{
D3D12_RECT scissorRect;
TileClipping tileClip = SetTileClip(poly.tileclip, scissorRect);
DrawState state;
state.scissor = CutScissor(tileClip == TileClipping::Outside ? scissorRect : baseScissor);
float trilinearAlpha = 1.f;
const bool trilinear = poly.tsp.FilterMode > 1 && poly.pcw.Texture && listType != ListType_Punch_Through && poly.tcw.MipMapped == 1;
if (trilinear)
{
trilinearAlpha = 0.25f * (poly.tsp.MipMapD & 0x3);
if (poly.tsp.FilterMode == 2)
// Trilinear pass A
trilinearAlpha = 1.f - trilinearAlpha;
}
const bool twoVolumes = poly.tsp1.full != (u32)-1 || poly.tcw1.full != (u32)-1;
const bool keepAll = twoVolumes || poly.tsp.SrcSelect;
const bool palette = BaseTextureCacheData::IsGpuHandledPaletted(poly.tsp, poly.tcw);
float palette_index = 0.f;
if (palette)
{
if (poly.tcw.PixelFmt == PixelPal4)
palette_index = (float(poly.tcw.PalSelect << 4) + 0.5f) / 1024.f;
else
palette_index = (float((poly.tcw.PalSelect >> 4) << 8) + 0.5f) / 1024.f;
}
PolyConstants constants = {};
constants.clipTest[0] = (float)scissorRect.left;
constants.clipTest[1] = (float)scissorRect.top;
constants.clipTest[2] = (float)scissorRect.right;
constants.clipTest[3] = (float)scissorRect.bottom;
constants.trilinearAlpha = trilinearAlpha;
constants.paletteIndex = palette_index;
u32 flags = 0;
if (listType == ListType_Punch_Through)
flags |= DX12_PF_ALPHA_TEST;
if ((poly.tileclip >> 28) == 3)
flags |= DX12_PF_CLIP_INSIDE;
if (poly.tsp.UseAlpha)
flags |= DX12_PF_USE_ALPHA;
if (poly.pcw.Texture)
flags |= DX12_PF_TEXTURE;
if (poly.tsp.IgnoreTexA || poly.tcw.PixelFmt == Pixel565)
flags |= DX12_PF_IGNORE_TEX_A;
flags |= (poly.tsp.ShadInstr & 3) << DX12_PF_SHAD_INSTR_SHIFT;
if (poly.pcw.Offset)
flags |= DX12_PF_OFFSET;
flags |= (poly.tsp.FogCtrl & 3) << DX12_PF_FOG_SHIFT;
if (poly.tcw.PixelFmt == PixelBumpMap)
flags |= DX12_PF_BUMPMAP;
if (poly.tsp.ColorClamp)
flags |= DX12_PF_CLAMP;
if (palette)
flags |= DX12_PF_PALETTE;
constants.flags = flags;
// The per-pixel pass drops a fragment whose blend would leave the
// pixel as it is. It cannot tell that when the source is the second
// accumulation buffer, or when the polygon has a second volume and
// which blend applies is known only when the lists are put
// together: those get a blend (8) that none of its cases take.
u32 oit = ((keepAll ? 8u : poly.tsp.SrcInstr) << DX12_OF_SRC_SHIFT) | ((keepAll ? 8u : poly.tsp.DstInstr) << DX12_OF_DST_SHIFT);
if (twoVolumes)
{
oit |= DX12_OF_TWO_VOLUMES | ((u32)poly.tsp1.SrcInstr << DX12_OF_SRC1_SHIFT) | ((u32)poly.tsp1.DstInstr << DX12_OF_DST1_SHIFT)
| ((u32)poly.tsp1.ShadInstr << DX12_OF_SHAD_INSTR1_SHIFT) | ((u32)poly.tsp1.FogCtrl << DX12_OF_FOG1_SHIFT);
if (poly.tsp1.UseAlpha)
oit |= DX12_OF_USE_ALPHA1;
if (poly.tsp1.IgnoreTexA)
oit |= DX12_OF_IGNORE_TEX_A1;
}
constants.oitFlags = oit;
constants.texture = DX12_SRV_NONE;
constants.texture1 = DX12_SRV_NONE;
if (poly.pcw.Texture)
{
if (poly.texid != 0)
constants.texture = ((DX12Texture *)(uintptr_t)poly.texid)->Srv();
if (twoVolumes && poly.texid1 != 0)
constants.texture1 = ((DX12Texture *)(uintptr_t)poly.texid1)->Srv();
}
constants.sampler = dx12_sampler_index(poly.tsp);
constants.sampler1 = dx12_sampler_index(poly.tsp1);
// (of a frame decoded on the GPU, the strip's number is its draw record's: the instance's)
constants.polyNumber = listType == ListType_Translucent && !pvrrc.gpu ? (u32)(&poly - pvrrc.global_param_tr.head()) : 0;
// Depth and stencil
DX12PsoKey key{};
key.kind = DX12_PSO_OIT;
key.pass = pass;
key.gouraud = poly.pcw.Gouraud;
key.depthBound = true;
key.depthTest = true;
if (pass == DX12_PASS_COLOR && !poly.isp.ZWriteDis && listType != ListType_Translucent)
key.depthFunc = 2; // equal
else if (listType == ListType_Punch_Through || autosort)
key.depthFunc = 6; // greater or equal
else
key.depthFunc = poly.isp.DepthMode;
/* Depth is written in the depth pass. In the colour pass opaque and
* punch-through polygons are drawn where their depth is the one the
* depth pass left, and translucent ones that are sorted only into the
* lists; the translucent polygons that are not sorted are drawn there,
* in order, each tested against the depth the ones before it wrote, so
* they still write. */
const bool colourPassWrites = pass == DX12_PASS_COLOR && !autosort && listType == ListType_Translucent;
if (pass != DX12_PASS_DEPTH && !colourPassWrites)
key.depthWrite = false;
// Z Write Disable seems to be ignored for punch-through.
// Fixes Worms World Party, Bust-a-Move 4 and Re-Volt
else if (listType == ListType_Punch_Through)
key.depthWrite = true;
else
key.depthWrite = !poly.isp.ZWriteDis;
state.stencilRef = 0;
if (pass == DX12_PASS_DEPTH && (listType == ListType_Opaque || listType == ListType_Punch_Through))
{
key.stencil = DX12Stencil::Shadow;
state.stencilRef = poly.pcw.Shadow != 0 ? 0x80 : 0;
}
else
key.stencil = DX12Stencil::None;
// Apparently punch-through polys support blending, or at least some combinations
if (listType == ListType_Punch_Through || pass == DX12_PASS_COLOR)
{
key.blend = true;
key.writeColor = true;
key.src = poly.tsp.SrcInstr;
key.dst = poly.tsp.DstInstr;
}
key.cull = poly.isp.CullMode == 3 ? 2 : poly.isp.CullMode == 2 ? 1 : 0;
// SSAA: shaded at each sample, or once a pixel - into the lists always
key.samples = context->TargetSamples();
if (key.samples != 0)
key.rate = pass == DX12_PASS_OIT || SsaaSingleRate(poly) ? DX12_RATE_PIXEL : DX12_RATE_SAMPLE;
state.pso = context->Pipelines().Get(key);
state.topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
u32 words[DX12Context::PolyConstantCount];
memcpy(words, &constants, sizeof(words));
// (a frame decoded on the GPU: a draw group by its arguments there)
if (pvrrc.gpu)
DrawGpu(state, words, listType, false, first, count);
else
Draw(state, words, count, first);
}
void DX12OITRenderer::DrawList(u32 listType, bool autosort, u32 pass, const List<PolyParam>& polys, u32 first, u32 last)
{
const PolyParam *pp_end = polys.head() + last;
for (const PolyParam *pp = polys.head() + first; pp != pp_end; pp++)
if (pp->count > 2)
DrawPoly(listType, autosort, pass, *pp, pp->first, pp->count);
FlushBatch();
}
/* The translucent modifier volumes' draws change the lists from their
* shaders, and nothing orders what the shaders of two draws do to the same
* fragment but a UAV barrier between them: a GPU runs draws at once, in any
* order, where a software device runs them one after the other. They go
* through here. Built with RACE_TEST (tools/rend/speckle_test.sh; never in
* a release) the draws between two barriers are recorded a triangle at a
* time, the draws' triangles taken in turn, the last draw's first: one
* draw's shaders run while another's are half done, as on a GPU, and a
* picture that changes then depends on an order nothing keeps. */
namespace {
struct ListDraws
{
DX12Context *context;
ID3D12Resource *lists;
#ifdef RACE_TEST
struct Held { ID3D12PipelineState *pso; D3D12_RECT scissor; u32 constants[DX12Context::PolyConstantCount]; u32 vertices, first; };
Held held[256];
u32 count = 0;
#endif
ListDraws(DX12Context *context, ID3D12Resource *lists) : context(context), lists(lists) {}
~ListDraws() { Flush(); }
void Draw(ID3D12PipelineState *pso, const D3D12_RECT& scissor, const u32 *constants, u32 vertices, u32 first)
{
#ifdef RACE_TEST
if (count == ARRAY_SIZE(held))
Flush();
Held& h = held[count++];
h.pso = pso;
h.scissor = scissor;
memcpy(h.constants, constants, sizeof(h.constants));
h.vertices = vertices;
h.first = first;
#else
Record(pso, scissor, constants, vertices, first);
#endif
}
/* what the draws before did to the lists is done before the draws after start */
void Barrier()
{
Flush();
context->UavBarrier(lists);
}
void Flush()
{
#ifdef RACE_TEST
for (u32 v = 0, more = count; more != 0; v += 3)
{
more = 0;
for (u32 i = count; i-- > 0; )
if (v < held[i].vertices)
{
Record(held[i].pso, held[i].scissor, held[i].constants, 3, held[i].first + v);
more++;
}
}
count = 0;
#endif
}
private:
void Record(ID3D12PipelineState *pso, const D3D12_RECT& scissor, const u32 *constants, u32 vertices, u32 first)
{
context->SetScissor(scissor);
context->SetPipeline(pso);
context->SetRootConstants(constants, DX12Context::PolyConstantCount);
context->Cmd()->DrawInstanced(vertices, 1, first, 0);
}
};
}
void DX12OITRenderer::DrawModifierVolumes(bool translucent, int first, int count, const ModifierVolumeParam *modVolParams)
{
if (count == 0 || !haveModVols)
return;
FlushBatch();
ID3D12GraphicsCommandList *cmd = context->Cmd();
// the modifier volumes' triangles, a position each
context->SetVertexBuffer(modVolView);
context->SetTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->SetScissor(CutScissor(baseScissor));
const ModifierVolumeParam* params = &modVolParams[first];
int mod_base = -1;
ListDraws listDraws(context, pixels.resource);
for (int cmv = 0; cmv < count; cmv++)
{
const ModifierVolumeParam& param = params[cmv];
u32 mvFirst, mvCount;
mv_clip_range_of(pvrrc, modVolParams, param, mvFirst, mvCount);
if (mvCount == 0)
continue;
u32 mv_mode = param.isp.DepthMode;
// clipped like a polygon, to the rectangle it was sent under if it asked to be
D3D12_RECT scissor;
{
D3D12_RECT clipRect;
if (SetTileClip(param.tileclip, clipRect) == TileClipping::Outside)
scissor = CutScissor(clipRect);
else
scissor = CutScissor(baseScissor);
context->SetScissor(scissor);
}
if (mod_base == -1)
mod_base = mvFirst;
const u32 cull = param.isp.CullMode == 3 ? 2 : param.isp.CullMode == 2 ? 1 : 0;
ID3D12PipelineState *pso;
u32 constants[DX12Context::PolyConstantCount] = {};
if (translucent)
{
// (the lists are marked by the shaders, and nothing else is tested or written)
DX12PsoKey key{};
key.kind = DX12_PSO_OIT_MODVOL;
key.depthFunc = 7;
key.cull = cull;
key.samples = context->TargetSamples();
key.rate = key.samples != 0 ? DX12_RATE_PIXEL : DX12_RATE_NONE;
pso = context->Pipelines().Get(key);
constants[13] = !param.isp.VolumeLast && mv_mode > 0 ? DX12_OF_MV_OR : DX12_OF_MV_XOR;
if (pso != nullptr)
listDraws.Draw(pso, scissor, constants, mvCount * 3, mvFirst * 3);
}
else
{
if (!param.isp.VolumeLast && mv_mode > 0)
pso = ModVolPipeline(true, false, 4, DX12Stencil::Or, cull, false, DX12_BLEND_KEY); // OR'ing (open volume or quad)
else
pso = ModVolPipeline(true, false, 4, DX12Stencil::Xor, cull, false, DX12_BLEND_KEY); // XOR'ing (closed volume)
if (pso != nullptr)
{
context->SetPipeline(pso);
context->SetStencilRef(2);
cmd->DrawInstanced(mvCount * 3, 1, mvFirst * 3, 0);
}
}
if (mv_mode == 1 || mv_mode == 2)
{
// Sum the area
if (translucent)
{
// (what the volumes before did to the lists is done)
listDraws.Barrier();
constants[13] = mv_mode == 1 ? DX12_OF_MV_INCLUSION : DX12_OF_MV_EXCLUSION;
if (pso != nullptr)
{
listDraws.Draw(pso, scissor, constants, (mvFirst + mvCount - mod_base) * 3, mod_base * 3);
listDraws.Barrier();
}
}
else
{
pso = ModVolPipeline(false, false, 7, mv_mode == 1 ? DX12Stencil::Inclusion : DX12Stencil::Exclusion, cull, false, DX12_BLEND_KEY);
if (pso != nullptr)
{
context->SetPipeline(pso);
context->SetStencilRef(1);
cmd->DrawInstanced((mvFirst + mvCount - mod_base) * 3, 1, mod_base * 3, 0);
}
}
mod_base = -1;
}
}
listDraws.Flush();
context->SetScissor(CutScissor(baseScissor));
context->SetVertexBuffer(vertexView);
}
/* The lists put together over the opaque polygons' picture (at @opaqueSrv
* of the table), into @targetRtv: the whole picture, not the part the game
* clips its drawing to - what is outside that has to be written too. With
* SSAA, from the samples: resolved into @target, of one sample, when
* @intoSamples is not set; else into the samples of @target, and the lists
* emptied after. */
bool DX12OITRenderer::MakeSums(u32 width, u32 height)
{
// (grown only, as the lists' heads are: its place is the one for good)
if (sums.resource && width <= sums.width && height <= sums.height)
return true;
width = std::max(width, sums.width);
height = std::max(height, sums.height);
DropSums();
D3D12_HEAP_PROPERTIES heap{};
heap.Type = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC desc{};
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Width = width;
desc.Height = height;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = DXGI_FORMAT_R16G16B16A16_UINT;
desc.SampleDesc.Count = 1;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
sums.state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
if (FAILED(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, sums.state, nullptr,
__uuidof(ID3D12Resource), (void **)sums.resource.put())))
{
WARN_LOG(RENDERER, "DX12: HDR channel sums creation failed (%d x %d)", width, height);
sums.resource.reset();
sums = DX12Image();
return false;
}
sums.width = width;
sums.height = height;
D3D12_UNORDERED_ACCESS_VIEW_DESC uav{};
uav.Format = DXGI_FORMAT_R16G16B16A16_UINT;
uav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
device->CreateUnorderedAccessView(sums.resource, nullptr, &uav, context->Srv().Cpu(DX12Context::OitSumsSlot));
return true;
}
void DX12OITRenderer::DropSums()
{
sumsStored = false;
if (!sums.resource)
return;
// (frames still on the GPU may store into it: it goes when they are done)
sums.resource->AddRef();
context->Release(sums.resource.get());
sums.resource.reset();
sums = DX12Image();
/* (its view stays in its place, unread: only the resolve that stores
* the sums reads it, and that one is drawn after a new one is made) */
}
void DX12OITRenderer::Resolve(u32 target, u32 opaqueSrv, bool intoSamples, bool withSums)
{
FlushBatch();
DX12PsoKey key{};
key.kind = DX12_PSO_OIT_FINAL;
key.writeColor = true;
key.depthFunc = 7;
if (SsaaActive())
{
key.rate = intoSamples ? DX12_RATE_SAMPLE : withSums ? DX12_RATE_PIXEL_SUMS : DX12_RATE_PIXEL;
key.samples = intoSamples ? ssaaLog2 : 0;
}
context->SetTargets(target, ~0u, key.samples);
ID3D12PipelineState *pso = context->Pipelines().Get(key);
if (pso == nullptr)
return;
context->SetPipeline(pso);
const D3D12_RECT area = CutScissor(D3D12_RECT{ 0, 0, (LONG)targetWidth, (LONG)targetHeight });
context->SetScissor(area);
context->SetTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
u32 constants[DX12Context::PolyConstantCount] = {};
constants[8] = opaqueSrv;
context->SetRootConstants(constants, DX12Context::PolyConstantCount);
context->Cmd()->DrawInstanced(4, 1, 0, 0);
if (key.rate == DX12_RATE_SAMPLE && clearHeap.Heap() != nullptr && area.right > area.left && area.bottom > area.top)
{
/* (the samples of a pixel all read its list: it is emptied once they
* are done, for the next render pass) */
ListsBarrier();
const UINT eol[4] = { ~0u, ~0u, ~0u, ~0u };
context->Cmd()->ClearUnorderedAccessViewUint(context->Srv().Gpu(DX12Context::OitSsaaFirst + 1), clearHeap.Cpu(0),
pointers.resource, eol, 1, &area);
}
}
void DX12OITRenderer::DrawFrame()
{
if (!MakePictures(targetWidth, targetHeight) || !UploadTrPolyParams() || !pixels.resource)
return;
context->SetViewport((float)targetWidth, (float)targetHeight);
ID3D12GraphicsCommandList *cmd = context->Cmd();
/* SSAA: the screen drawn into the pictures of samples, with the views of
* their depth and stencil in the table the shaders read */
const bool ssaa = SsaaActive();
DX12Image *pictures = ssaa ? ssaaColor : this->pictures;
const u32 samples = ssaa ? ssaaLog2 : 0;
context->BindOitTable(ssaa);
/* (the HDR output's: the screen's resolve stores the samples' channel
* sums as well, where they can be had) */
const bool withSums = ssaa && !pvrrc.isRTT && context->HdrActive() && MakeSums(targetWidth, targetHeight);
if (!pvrrc.isRTT)
sumsStored = false;
/* A framebuffer the game has not drawn to lately starts as the border
* colour, which is what the OpenGL renderers clear it to (never a
* render to a texture: that is not such a framebuffer); any other
* starts black. */
float clear[4] = { 0.f, 0.f, 0.f, 1.f };
if (pvrrc.clearFramebuffer)
{
clear[0] = VO_BORDER_COL.Red / 255.f;
clear[1] = VO_BORDER_COL.Green / 255.f;
clear[2] = VO_BORDER_COL.Blue / 255.f;
}
for (int i = 0; i < 2; i++)
context->Transition(pictures[i], D3D12_RESOURCE_STATE_RENDER_TARGET);
cmd->ClearRenderTargetView(context->Rtv().Cpu(pictures[0].rtv), clear, 0, nullptr);
if (pvrrc.isRTT)
// (what is outside the pixel clip, as the per-triangle renderer leaves it)
cmd->ClearRenderTargetView(context->Rtv().Cpu(targetRtv), clear, 0, nullptr);
DepthStates(D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_DEPTH_WRITE);
cmd->ClearDepthStencilView(context->Dsv().Cpu(depth.rtv), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 0.f, 0, 0, nullptr);
/* In bands, when the frames before had more translucent pixels than the
* lists have room for (ChooseBands()): all of the frame's render passes
* once a band, every draw cut to the band by the scissor (CutScissor()),
* each band's lists starting empty and put on the picture before the
* next band's. Nothing is dropped and nothing is allocated; the opaque
* polygons are drawn once a band. (The clears above are of the whole
* target: a band's draws touch its own pixels alone, so the others'
* depth is still clear when their band comes.) */
const u32 bands = std::max<u32>(std::min<u32>(ChooseBands(pvrrc.isRTT), std::max<u32>(targetHeight, 1)), 1);
const u32 bandHeight = (targetHeight + bands - 1) / bands;
const u32 bandCount = std::max<u32>((targetHeight + bandHeight - 1) / bandHeight, 1);
countWords[countHead] = 0;
u32 countWord = 0;
const int render_pass_count = pvrrc.render_passes.used();
const D3D12_RECT whole = { 0, 0, (LONG)targetWidth, (LONG)targetHeight };
for (u32 bandIndex = 0; bandIndex < bandCount; bandIndex++)
{
band.top = (LONG)(bandIndex * bandHeight);
band.bottom = bandCount > 1 ? (LONG)std::min<u32>((bandIndex + 1) * bandHeight, targetHeight) : 0;
int cur = 0;
RenderPass previous_pass = {};
for (int render_pass = 0; render_pass < render_pass_count; render_pass++)
{
const RenderPass& current_pass = pvrrc.render_passes.head()[render_pass];
DEBUG_LOG(RENDERER, "Render pass %d OP %d PT %d TR %d MV %d TrMV %d autosort %d", render_pass + 1,
current_pass.op_count - previous_pass.op_count,
current_pass.pt_count - previous_pass.pt_count,
current_pass.tr_count - previous_pass.tr_count,
current_pass.mvo_count - previous_pass.mvo_count,
current_pass.mvo_tr_count - previous_pass.mvo_tr_count, current_pass.autosort);
const bool finalPass = render_pass == render_pass_count - 1;
if (render_pass != 0)
// (each pass has its own volumes)
ClearStencil();
// Depth and stencil. The pixel counter is reset: the lists are
// empty, and the buffer is filled from its start.
ResetCount();
DepthStates(D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_DEPTH_WRITE);
context->Transition(pictures[cur], D3D12_RESOURCE_STATE_RENDER_TARGET);
context->SetTargets(pictures[cur].rtv, depth.rtv, samples);
DrawList(ListType_Opaque, false, DX12_PASS_DEPTH, pvrrc.global_param_op, previous_pass.op_count, current_pass.op_count);
DrawList(ListType_Punch_Through, false, DX12_PASS_DEPTH, pvrrc.global_param_pt, previous_pass.pt_count, current_pass.pt_count);
DrawModifierVolumes(false, previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count, pvrrc.global_param_mvo.head());
// Colour, the stencil read by the shaders
DepthStates(D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
context->SetTargets(pictures[cur].rtv, dsvReadStencil, samples);
/* A render that covers only part of the screen leaves the rest as
* it was: the last picture goes in first, under everything */
if (render_pass == 0 && !pvrrc.isRTT && KeepsLastPicture())
{
DX12Image *last = LastPictureImage();
if (last != nullptr && (!context->IsHandedOver(last) || last->state == D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
{
context->Transition(*last, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
// (the quad is drawn with the scissor that is set: the band's)
context->SetScissor(CutScissor(whole));
DX12Quad::Draw(last->srv, !havePicture, -1.f, -1.f, 2.f, 2.f);
}
}
// OP + PT
DrawList(ListType_Opaque, false, DX12_PASS_COLOR, pvrrc.global_param_op, previous_pass.op_count, current_pass.op_count);
DrawList(ListType_Punch_Through, false, DX12_PASS_COLOR, pvrrc.global_param_pt, previous_pass.pt_count, current_pass.pt_count);
// TR
/* (In the first frame after the per-pixel lists are made, too: they
* start empty, and that frame's translucent polygons are drawn like
* any other's.) */
if (current_pass.autosort)
{
// into the lists, tested against the depth by the shaders as well
DepthStates(D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_DEPTH_READ);
context->SetTargets(pictures[cur].rtv, dsvReadOnly, samples);
DrawList(ListType_Translucent, true, DX12_PASS_OIT, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count);
ListsBarrier();
// how many pixels this band's pass asked for, copied out
CopyCount(countHead, countWord++);
}
else
DrawList(ListType_Translucent, false, DX12_PASS_COLOR, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count);
// The translucent modifier volumes, and the lists put together
const u32 target = finalPass ? targetRtv : pictures[cur ^ 1].rtv;
/* (nothing is written to it; with SSAA the volumes are drawn with the
* samples, for where they cover a pixel's first sample) */
context->SetTargets(ssaa ? pictures[cur].rtv : target, ~0u, samples);
if (current_pass.mv_op_tr_shared)
DrawModifierVolumes(true, previous_pass.mvo_count, current_pass.mvo_count - previous_pass.mvo_count, pvrrc.global_param_mvo.head());
else
DrawModifierVolumes(true, previous_pass.mvo_tr_count, current_pass.mvo_tr_count - previous_pass.mvo_tr_count, pvrrc.global_param_mvo_tr.head());
ListsBarrier();
context->Transition(pictures[cur], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
if (finalPass && withSums)
context->Transition(sums, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
Resolve(target, pictures[cur].srv, !finalPass, finalPass && withSums);
if (finalPass && withSums)
sumsStored = true;
ListsBarrier();
if (!finalPass)
{
// The translucent polygons' depth, for the passes after this one
DepthStates(D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_DEPTH_WRITE);
context->Transition(pictures[cur], D3D12_RESOURCE_STATE_RENDER_TARGET);
context->SetTargets(pictures[cur].rtv, depth.rtv, samples);
DrawList(ListType_Translucent, current_pass.autosort, DX12_PASS_DEPTH, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count);
// the next pass draws on what this one put together
cur ^= 1;
}
previous_pass = current_pass;
}
}
band = {};
// (what is drawn on the picture after this is not cut to the last band; the depth as the base renderer has it)
context->SetScissor(whole);
DepthStates(D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_DEPTH_WRITE);
// the frame's counts are in its slot: the next frame's go in the next
countBands[countHead] = bandCount;
countRtt[countHead] = pvrrc.isRTT;
countHead = (countHead + 1) % COUNT_SLOTS;
// (the target is left bound: the overlay goes onto it, and the end of the render takes it; with SSAA, its depth is of samples)
context->SetTargets(targetRtv, ssaa ? ~0u : depth.rtv);
}
/* ---- Rendering Method: Compute ---- */
bool DX12OITRenderer::RenderFrame()
{
/* (a render to a texture with an empty pixel clip writes nothing: Render()'s) */
if (compute.Ready() && device != nullptr && !pvrrc.isRenderFramebuffer
&& !(pvrrc.isRTT && (pvrrc.fb_X_CLIP.max < pvrrc.fb_X_CLIP.min || pvrrc.fb_Y_CLIP.max < pvrrc.fb_Y_CLIP.min)))
{
if (pvrrc.isRTT && !texPool.TargetsStorable())
compute.Fallback(ComputeFrame::Why_Rtt);
else if (!compute.CanDraw())
compute.Fallback(ComputeFrame::Why_Decode);
else if (pvrrc.isRTT ? RenderComputeRtt() : RenderCompute())
return !pvrrc.isRTT;
}
return DX12Renderer::RenderFrame();
}
bool DX12OITRenderer::RenderComputeRtt()
{
u32 width, height;
RttSize(width, height);
computeRules.renderer = this;
if (!compute.Prepare(computeRules, ~0u, false, width, height))
return false;
// (the frame's textures were looked at; the texture drawn into is taken now, which can be one of them)
if (!BeginRTT())
{
rttTexture = nullptr;
return false;
}
DX12PooledTexture *spare = rttTexture->Spare();
context->Transition(paletteTexture, (D3D12_RESOURCE_STATES)(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE));
context->Transition(spare->resource, spare->state, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
compute.Record(spare->resource);
EndRTT();
return true;
}
bool DX12OITRenderer::RenderCompute()
{
/* the frontend may still be showing what is in the screen's next
* picture: it is waited for, if Process() has not (and not again by the
* per-pixel renderer, if it draws the frame after all) */
if (!heldFromProcess)
context->WaitPresentIndex();
heldFromProcess = true;
SsaaDepth(false);
screenIndex = (int)context->GetPresentIndex();
if (!EnsureScreen(screen_width, screen_height))
return false;
ScreenTarget& target = screenTargets[screenIndex];
// (made for the screen's size: Resize() makes it again)
if (compute.Width() != screenWidth || compute.Height() != screenHeight)
{
compute.Fallback(ComputeFrame::Why_Room);
return false;
}
matrices.CalcMatrices(&pvrrc);
// the frame drawn under this frame's jitter, where the walks store the depth for the upscaler
const bool forUpscaler = compute.DepthOn() && UpscalerJitter();
/* The picture to start from, where the render covers only part of the
* screen: read where it is; or copied first where it cannot be - this
* frame's own picture (the frontend's one picture, version 1), or one
* the frontend has (version 2), which it leaves in COPY_SOURCE with the
* frontends there are, as BeginScreen() copies it */
u32 last = ~0u;
bool scaled = false;
bool copy = false;
DX12Image *image = KeepsLastPicture() ? LastPictureImage() : nullptr;
if (image != nullptr)
{
const bool handedOver = context->IsHandedOver(image);
copy = image == &target.image || handedOver;
if (copy && (!havePicture || image->width != compute.Width() || image->height != compute.Height()
|| (handedOver && image->state != D3D12_RESOURCE_STATE_COPY_SOURCE)))
{
compute.Fallback(ComputeFrame::Why_Picture);
return false;
}
last = copy ? compute.StartSrv() : image->srv;
// (the picture kept from another context or size: filtered over this one, as BeginScreen() draws it)
scaled = !havePicture;
}
computeRules.renderer = this;
if (!compute.Prepare(computeRules, last, scaled))
return false;
// (the upscaler's strips matched once the frame is this renderer's: the raster path matches its own)
UpscalerSlot *slot = forUpscaler && UpscalerMatch() ? &upscalerSlots[screenIndex] : nullptr;
if (image != nullptr && copy)
{
context->Transition(*image, D3D12_RESOURCE_STATE_COPY_SOURCE);
compute.CopyStart(image->resource);
}
else if (image != nullptr)
context->Transition(*image, (D3D12_RESOURCE_STATES)(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE));
context->Transition(paletteTexture, (D3D12_RESOURCE_STATES)(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE));
context->Transition(target.image, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
// (the HDR output's channel sums, stored by the walk: the per-pixel renderer's image of them)
const bool withSums = compute.SumsOn() && MakeSums(screenWidth, screenHeight);
if (withSums)
context->Transition(sums, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
if (slot != nullptr)
context->Transition(slot->depth, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
compute.Record(target.image.resource, withSums ? sums.resource.get() : nullptr, slot != nullptr ? slot->depth.resource.get() : nullptr);
sumsStored = withSums && compute.SumsStored();
/* the motion vector pass at the end of the frame, over the depth the
* walk stored, from the frame's vertices where the walk read them (with
* the constants and scissor the pass draws with: the raster path's) */
if (slot != nullptr && compute.DepthStored() && UploadConstants())
{
SetBaseScissor();
compute.RingForDraw(motionVertexView, motionIndexView);
motionSampled = true;
}
else
upscaled = false;
// (what goes over it is drawn into it, and it is handed over, as any other frame of the screen)
context->Transition(target.image, D3D12_RESOURCE_STATE_RENDER_TARGET);
EndScreen();
heldFromProcess = false;
return true;
}
Renderer* rend_OITD3D12()
{
return new DX12OITRenderer();
}