diff --git a/.github/workflows/vulkan-negotiation.yml b/.github/workflows/vulkan-negotiation.yml index c9c11780..14ec106f 100644 --- a/.github/workflows/vulkan-negotiation.yml +++ b/.github/workflows/vulkan-negotiation.yml @@ -30,8 +30,9 @@ name: Vulkan negotiation # And a frame's vertices and indices parsed straight into memory the card # reads, none copied, the pictures those of the lists copied, with Vulkan # and with OpenGL (tools/vulkan/vk_parse_arena.sh, tools/gl/gl_parse_arena.sh: -# no OpenGL error either, and OpenGL ES drawing as ever), and the arena's -# slices (tools/rend/parse_arena_test.sh). +# no OpenGL error either, and OpenGL ES drawing as ever), and with Direct3D +# 12 where the adapter's memory is the processor's (tools/rend/d3d_parse_arena.sh, +# under wine), and the arena's slices (tools/rend/parse_arena_test.sh). on: push: @@ -66,6 +67,7 @@ on: - 'core/hw/pvr/ta_list.*' - 'core/hw/pvr/helper_classes.h' - 'tools/rend/parse_arena_test.*' + - 'tools/rend/d3d_parse_arena.sh' - 'tools/rend/clip_check.py' - 'tools/threads/live_prog.c' - 'Makefile*' @@ -102,6 +104,7 @@ on: - 'core/hw/pvr/ta_list.*' - 'core/hw/pvr/helper_classes.h' - 'tools/rend/parse_arena_test.*' + - 'tools/rend/d3d_parse_arena.sh' - 'tools/rend/clip_check.py' - 'tools/threads/live_prog.c' - 'Makefile*' @@ -180,3 +183,5 @@ jobs: run: VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json sh tools/hdr/d3d_hdr.sh parallel_dc_libretro.dll - name: Direct3D 11 - video memory written by DMA over renders to textures (Direct3D 12 with D3D12_DLLS) run: VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json sh tools/rend/d3d_dma.sh parallel_dc_libretro.dll + - name: Direct3D 12 drawing the frame from where it is parsed, every frame the one of the lists copied + run: VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json sh tools/rend/d3d_parse_arena.sh parallel_dc_libretro.dll diff --git a/Makefile.common b/Makefile.common index a2f3fd63..0fdddb57 100644 --- a/Makefile.common +++ b/Makefile.common @@ -325,7 +325,8 @@ ifeq ($(HAVE_D3D12), 1) $(CORE_DIR)/core/rend/dx12/dx12_texdecoder.cpp \ $(CORE_DIR)/core/rend/dx12/dx12_texture.cpp \ $(CORE_DIR)/core/rend/dx12/oit/dx12_oitrenderer.cpp - SOURCES_C += $(CORE_DIR)/core/rend/hdr/hdr_encode_d3d12.c + SOURCES_C += $(CORE_DIR)/core/rend/hdr/hdr_encode_d3d12.c \ + $(CORE_DIR)/core/rend/dx12/parse_arena_d3d12.c endif # The HDR output's Display Transform (rend/hdr/, Color Format), for the diff --git a/core/rend/dx12/dx12_renderer.cpp b/core/rend/dx12/dx12_renderer.cpp index 2ae6fa21..313f1caf 100644 --- a/core/rend/dx12/dx12_renderer.cpp +++ b/core/rend/dx12/dx12_renderer.cpp @@ -238,6 +238,17 @@ bool DX12Renderer::Init() writeOut.Init(context); writeOutPictures = DX12WriteOut::Wanted(); SetupSsaa(screen_width, screen_height); + // where frames are parsed when the card can draw them from there: made once + if (!parseArena.ready) + { + size_t room[PARSE_ARENA_PARTS]; + TA_context::ArenaRooms(room); + arenaMissed = false; + if (parse_arena_d3d12_init(&parseArena, device, context->GetFence(), room, 4)) + NOTICE_LOG(RENDERER, "DX12: renders parsed where the card reads them, not copied there"); + else + INFO_LOG(RENDERER, "DX12: renders copied to the card (its memory is not the processor's, cache-coherent)"); + } SetupUpscaler(); /* GPU-side TA processing, if it is on: here, not in a frame; and the * buffer its frames' vertex constants go to (UploadConstants()) */ @@ -297,6 +308,8 @@ void DX12Renderer::Term() gpuVtxConstants.reset(); } gpuVtxConstantsState = D3D12_RESOURCE_STATE_COPY_DEST; + // (let go once the frames drawn from it are done) + parse_arena_d3d12_term(&parseArena, [](void *c, IUnknown *object) { ((DX12Context *)c)->Release(object); }, context); overlay.Term(); DropImage(fbTexture); DropRestored(); @@ -548,7 +561,22 @@ bool DX12Renderer::ProcessLocked(TA_context* ctx) * recorded now; or it is parsed as ever */ bool result; if (!taDecoder.Ready() || !taDecoder.Prepare(ctx, TaFlags(), &result)) + { + /* into a slice of the memory the card reads, where there is one + * free: the draws read it there, uncopied (with the background + * polygon's four vertices FillBGP() wrote, at the head of the list + * the parse empties and takes them back in) */ + ta_list_t *lists[PARSE_ARENA_PARTS]; + ctx->ArenaLists(lists); + if (!parse_arena_d3d12_attach(&parseArena, lists, 4 * sizeof(Vertex)) && parseArena.ready && !arenaMissed) + { + arenaMissed = true; + NOTICE_LOG(RENDERER, "DX12: a render copied to the card, no slice of the parse's free"); + } result = ta_parse_vdrc(ctx); + if (!result) + parse_arena_d3d12_cancel(&parseArena); + } else if (result) taDecoder.Record(ctx); // the textures the frame's polygons asked for that are made on the GPU, before the draws @@ -694,6 +722,28 @@ bool DX12Renderer::UploadGeometry() const u32 indexBytes = pvrrc.idx.bytes() + SortPassesRoom() * sizeof(u32); const List& modVolList = pvrrc.mv_clip.used() != 0 ? pvrrc.mv_clip : pvrrc.modtrig; const u32 modVolBytes = modVolList.bytes(); + /* Parsed where the card reads it (parseArena): drawn from there, the + * sorted passes' indices after the frame's, in the room they have + * there - or, with more of them, copied as below */ + const D3D12_GPU_VIRTUAL_ADDRESS here = parse_arena_d3d12_gpu(&parseArena, PARSE_ARENA_VERTS); + if (here != 0 && indexBytes <= parseArena.arena.space[PARSE_ARENA_IDX]) + { + // sorted from the vertices as the game sent them, then those made for the card, in place + SortTriangles(pvrrc.idx.head() + pvrrc.idx.used()); + SetProvokingVertices(); + vertexView.BufferLocation = here; + vertexView.SizeInBytes = vertexBytes; + vertexView.StrideInBytes = sizeof(Vertex); + indexView.BufferLocation = parse_arena_d3d12_gpu(&parseArena, PARSE_ARENA_IDX); + indexView.SizeInBytes = pvrrc.idx.bytes() + sortedIndexCount * sizeof(u32); + indexView.Format = DXGI_FORMAT_R32_UINT; + modVolView.BufferLocation = parse_arena_d3d12_gpu(&parseArena, + pvrrc.mv_clip.used() != 0 ? PARSE_ARENA_MV_CLIP : PARSE_ARENA_MODTRIG); + modVolView.SizeInBytes = modVolBytes; + modVolView.StrideInBytes = sizeof(float) * 3; + haveModVols = modVolBytes != 0; + return UploadConstants(); + } /* the vertices, the indexes, the volumes' triangles, each at a multiple * of its size, in one piece of the ring */ const u32 indexes = (vertexBytes + 3) & ~3u; @@ -1627,6 +1677,15 @@ void DX12Renderer::DrawFramebuffer() /* ---- The frame ---- */ bool DX12Renderer::Render() +{ + const bool rv = RenderFrame(); + /* (the commands that draw it: submitted already, or in the list the next + * submission executes) */ + parse_arena_d3d12_drawn(&parseArena, context->IsRecording() ? context->LastSubmitted() + 1 : context->LastSubmitted()); + return rv; +} + +bool DX12Renderer::RenderFrame() { const bool held = heldFromProcess; heldFromProcess = false; diff --git a/core/rend/dx12/dx12_renderer.h b/core/rend/dx12/dx12_renderer.h index b1b8fdd1..68fd58a4 100644 --- a/core/rend/dx12/dx12_renderer.h +++ b/core/rend/dx12/dx12_renderer.h @@ -28,6 +28,7 @@ #include "rend/transform_matrix.h" #include "rend/shadows.h" #include "dx12context_lr.h" +#include "parse_arena_d3d12.h" #include "dx12_overlay.h" #include "dx12_writeout.h" #include "dx12_texdecoder.h" @@ -98,13 +99,21 @@ public: void Resize(int w, int h) override; void Term() override; bool Process(TA_context* ctx) override; - bool Render() override; + /* The frame drawn (RenderFrame()), and its lists its own again: what the + * parse wrote where the card reads it (parseArena) the card's till the + * commands that draw it are done */ + bool Render() final; void KeepPicture() override; u64 GetTexture(TSP tsp, TCW tcw, int area = 0) override; // (TA processing switched on or off) void OptionsChanged() override; protected: + virtual bool RenderFrame(); + /* Where a frame is parsed when the card can draw it from there: a + * machine whose memory is the processor's (parse_arena_d3d12.h) */ + parse_arena_d3d12_t parseArena {}; + bool arenaMissed = false; // (said once: a render copied, no slice free) struct VertexConstants { float scale[4]; // x, y, z, and the depth's scale (shaders/dx12_raster.hlsl) diff --git a/core/rend/dx12/oit/dx12_oitrenderer.cpp b/core/rend/dx12/oit/dx12_oitrenderer.cpp index 6ed762dc..295e688e 100644 --- a/core/rend/dx12/oit/dx12_oitrenderer.cpp +++ b/core/rend/dx12/oit/dx12_oitrenderer.cpp @@ -123,7 +123,7 @@ public: InitCompute(); } - bool Render() override; + bool RenderFrame() override; void Term() override { @@ -1351,7 +1351,7 @@ void DX12OITRenderer::DrawFrame() /* ---- Rendering Method: Compute ---- */ -bool DX12OITRenderer::Render() +bool DX12OITRenderer::RenderFrame() { /* (a render to a texture with an empty pixel clip writes nothing: Render()'s) */ if (compute.Ready() && device != nullptr && !pvrrc.isRenderFramebuffer @@ -1364,7 +1364,7 @@ bool DX12OITRenderer::Render() else if (pvrrc.isRTT ? RenderComputeRtt() : RenderCompute()) return !pvrrc.isRTT; } - return DX12Renderer::Render(); + return DX12Renderer::RenderFrame(); } bool DX12OITRenderer::RenderComputeRtt() diff --git a/core/rend/dx12/parse_arena_d3d12.c b/core/rend/dx12/parse_arena_d3d12.c new file mode 100644 index 00000000..e994cbe4 --- /dev/null +++ b/core/rend/dx12/parse_arena_d3d12.c @@ -0,0 +1,119 @@ +/* See parse_arena_d3d12.h. C89. */ +#ifndef COBJMACROS +#define COBJMACROS +#endif +#include +#include "parse_arena_d3d12.h" + +/* (ID3D12Resource's, here: nothing is linked for it) */ +static const GUID resource_iid = { 0x696442be, 0xa72e, 0x4059, { 0xbc, 0x79, 0x5b, 0x5c, 0x98, 0x04, 0x0f, 0xad } }; + +int parse_arena_d3d12_init(parse_arena_d3d12_t *a, ID3D12Device *device, ID3D12Fence *fence, + const size_t *room, unsigned slots) +{ + D3D12_FEATURE_DATA_ARCHITECTURE arch; + D3D12_HEAP_PROPERTIES heap; + D3D12_RESOURCE_DESC desc; + D3D12_RANGE none; + void *map = NULL; + size_t bytes; + + memset(a, 0, sizeof(*a)); + memset(&arch, 0, sizeof(arch)); + if (FAILED(ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_ARCHITECTURE, &arch, sizeof(arch))) + || !arch.CacheCoherentUMA) + return 0; + /* (a slice's parts 256 bytes aligned: more than a vertex or index + * buffer's view needs) */ + bytes = parse_arena_layout(&a->arena, room, slots, 256); + if (bytes == 0) + return 0; + memset(&heap, 0, sizeof(heap)); + heap.Type = D3D12_HEAP_TYPE_CUSTOM; + heap.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_WRITE_BACK; + heap.MemoryPoolPreference = D3D12_MEMORY_POOL_L0; + memset(&desc, 0, sizeof(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; + if (FAILED(ID3D12Device_CreateCommittedResource(device, &heap, D3D12_HEAP_FLAG_NONE, &desc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &resource_iid, (void **)&a->buffer))) + { + a->buffer = NULL; + return 0; + } + /* (nothing read through the mapping yet) */ + none.Begin = 0; + none.End = 0; + if (FAILED(ID3D12Resource_Map(a->buffer, 0, &none, &map)) || map == NULL) + { + ID3D12Resource_Release(a->buffer); + a->buffer = NULL; + return 0; + } + parse_arena_bind(&a->arena, map); + a->gpu = ID3D12Resource_GetGPUVirtualAddress(a->buffer); + a->fence = fence; + a->ready = 1; + return 1; +} + +void parse_arena_d3d12_term(parse_arena_d3d12_t *a, void (*release)(void *arg, IUnknown *object), void *arg) +{ + if (a->ready) + parse_arena_detach(&a->arena); + if (a->buffer != NULL) + { + if (release != NULL) + release(arg, (IUnknown *)a->buffer); + else + ID3D12Resource_Release(a->buffer); + } + memset(a, 0, sizeof(*a)); +} + +int parse_arena_d3d12_attach(parse_arena_d3d12_t *a, ta_list_t *const *lists, size_t vert_head) +{ + UINT64 done; + unsigned i; + + if (!a->ready || a->arena.slot >= 0) + return 0; + done = ID3D12Fence_GetCompletedValue(a->fence); + for (i = 0; i < a->arena.slices; i++) + { + const unsigned s = (a->next + i) % a->arena.slices; + if (a->drawn[s] > done) + continue; + if (!parse_arena_attach(&a->arena, s, lists, vert_head)) + return 0; + a->next = (s + 1) % a->arena.slices; + return 1; + } + return 0; +} + +void parse_arena_d3d12_drawn(parse_arena_d3d12_t *a, UINT64 value) +{ + if (!a->ready || a->arena.slot < 0) + return; + a->drawn[a->arena.slot] = value; + parse_arena_detach(&a->arena); +} + +void parse_arena_d3d12_cancel(parse_arena_d3d12_t *a) +{ + if (a->ready) + parse_arena_detach(&a->arena); +} + +D3D12_GPU_VIRTUAL_ADDRESS parse_arena_d3d12_gpu(const parse_arena_d3d12_t *a, unsigned part) +{ + if (!a->ready || a->arena.slot < 0 || part >= PARSE_ARENA_PARTS) + return 0; + return a->gpu + parse_arena_offset(&a->arena, (unsigned)a->arena.slot, part); +} diff --git a/core/rend/dx12/parse_arena_d3d12.h b/core/rend/dx12/parse_arena_d3d12.h new file mode 100644 index 00000000..21cd7f8f --- /dev/null +++ b/core/rend/dx12/parse_arena_d3d12.h @@ -0,0 +1,61 @@ +#ifndef PARSE_ARENA_D3D12_H +#define PARSE_ARENA_D3D12_H +/* The parse's arena (rend/parse_arena.h) with Direct3D 12: one buffer of + * a custom heap with the processor's own, cached pages (write-back) in the + * memory the card reads, mapped for good, in slices for a few frames in + * flight, where the frame is parsed and then drawn from: no copy into the + * upload ring. Only where the adapter's memory is the processor's, kept + * coherent with its cache (CacheCoherentUMA): elsewhere such pages are + * read over the bus, and the frame is copied, as ever. + * + * A slice is taken when the context's fence has passed the value it was + * last drawn with (polled: nothing waits); with none free, the frame is + * copied. C89. */ +#include +#include "../parse_arena.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct parse_arena_d3d12 +{ + parse_arena_t arena; + ID3D12Resource *buffer; + D3D12_GPU_VIRTUAL_ADDRESS gpu; + ID3D12Fence *fence; /* the context's (not this one's to release) */ + UINT64 drawn[PARSE_ARENA_SLOTS]; /* the fence's value once a slice is done with */ + unsigned next; /* the slice looked at first */ + int ready; /* (all zero, as a static one is before it is made: none) */ +} parse_arena_d3d12_t; + +/* Made on @device for @slots frames of lists of the rooms @room (by + * PARSE_ARENA_*), its slices free when @fence says so; false, and nothing + * kept, where the adapter is not CacheCoherentUMA or the buffer cannot be + * made and mapped */ +int parse_arena_d3d12_init(parse_arena_d3d12_t *a, ID3D12Device *device, ID3D12Fence *fence, + const size_t *room, unsigned slots); +/* (the frames drawn from it done on the card; @buffer is given to + * @release where the context lets it go later, or released now) */ +void parse_arena_d3d12_term(parse_arena_d3d12_t *a, void (*release)(void *arg, IUnknown *object), void *arg); + +/* The frame whose lists these are (@lists, by PARSE_ARENA_*; @vert_head as + * parse_arena_attach()'s) parsed into a free slice, till + * parse_arena_d3d12_drawn() or parse_arena_d3d12_cancel(); false where + * none is free or the lists are not of the arena's rooms */ +int parse_arena_d3d12_attach(parse_arena_d3d12_t *a, ta_list_t *const *lists, size_t vert_head); +/* ...drawn by the commands the fence's @value is signalled after: its + * slice the card's till then, and its lists its own again */ +void parse_arena_d3d12_drawn(parse_arena_d3d12_t *a, UINT64 value); +/* ...not drawn: its lists its own again, and its slice free */ +void parse_arena_d3d12_cancel(parse_arena_d3d12_t *a); + +/* Where part @part of the frame's slice is, for the card; 0 when no frame + * is attached */ +D3D12_GPU_VIRTUAL_ADDRESS parse_arena_d3d12_gpu(const parse_arena_d3d12_t *a, unsigned part); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/hdr/d3d_hdr.c b/tools/hdr/d3d_hdr.c index 7203a1c4..c54ede1b 100644 --- a/tools/hdr/d3d_hdr.c +++ b/tools/hdr/d3d_hdr.c @@ -15,6 +15,14 @@ * frame's motion vectors and depth, which are read back and looked at as * vk_hdr.c looks at the Vulkan renderers'. * + * With D3DH_NOT_UMA=1 (Direct3D 12) the adapter says its memory is not the + * processor's (CacheCoherentUMA 0, as a card of its own has it): the core + * copies the frame's lists to the card, not parse them where it reads + * them (tools/rend/d3d_parse_arena.sh). With D3DH_EVERY=1 (Direct3D 12) + * the picture handed over is read back after every frame and its sum + * printed, "frame N SUM", for runs to be compared frame for frame (as + * gl_frames.c's GLF_EVERY). + * * D3DH_API=11|12 D3DH_FRONTEND=sdr|hdr10|scrgb D3DH_DIR=dir \ * d3d_hdr core.dll content out.raw frames [key=value ...] * @@ -79,6 +87,29 @@ static struct retro_hw_render_interface_d3d11 iface11; /* ---- Direct3D 12, version 1: the frame is handed by set_texture ---- */ static ID3D12Device *d12; + +/* D3DH_NOT_UMA: the device's CheckFeatureSupport(), its answer about the + * architecture changed, through a copy of its table of methods - a table + * the newer interfaces of the object (ID3D12Device1 and on, which the + * core asks for) share and go on past ID3D12Device's in: so a good many + * more of its methods than the headers here know copied too */ +static void *d12_not_uma[192]; +static HRESULT (STDMETHODCALLTYPE *d12_check)(ID3D12Device *, D3D12_FEATURE, void *, UINT); +static HRESULT STDMETHODCALLTYPE not_uma_check(ID3D12Device *device, D3D12_FEATURE feature, void *data, UINT size) +{ + const HRESULT hr = d12_check(device, feature, data, size); + if (SUCCEEDED(hr) && feature == D3D12_FEATURE_ARCHITECTURE && size >= sizeof(D3D12_FEATURE_DATA_ARCHITECTURE)) + { + ((D3D12_FEATURE_DATA_ARCHITECTURE *)data)->UMA = FALSE; + ((D3D12_FEATURE_DATA_ARCHITECTURE *)data)->CacheCoherentUMA = FALSE; + } + if (SUCCEEDED(hr) && feature == D3D12_FEATURE_ARCHITECTURE1 && size >= sizeof(D3D12_FEATURE_DATA_ARCHITECTURE1)) + { + ((D3D12_FEATURE_DATA_ARCHITECTURE1 *)data)->UMA = FALSE; + ((D3D12_FEATURE_DATA_ARCHITECTURE1 *)data)->CacheCoherentUMA = FALSE; + } + return hr; +} static ID3D12CommandQueue *q12; static ID3D12Resource *frame12; static DXGI_FORMAT frame12_format; @@ -601,6 +632,13 @@ int main(int argc, char **argv) fprintf(stderr, "d3d_hdr: no Direct3D 12 device\n"); return 2; } + if (getenv("D3DH_NOT_UMA")) + { + memcpy(d12_not_uma, d12->lpVtbl, sizeof(d12_not_uma)); + d12_check = d12->lpVtbl->CheckFeatureSupport; + ((ID3D12DeviceVtbl *)d12_not_uma)->CheckFeatureSupport = not_uma_check; + d12->lpVtbl = (ID3D12DeviceVtbl *)d12_not_uma; + } memset(&qd, 0, sizeof(qd)); qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; if (FAILED(ID3D12Device_CreateCommandQueue(d12, &qd, &IID_ID3D12CommandQueue, (void**)&q12))) @@ -656,7 +694,19 @@ int main(int argc, char **argv) } hw.context_reset(); for (n = 0; n < frames; n++) + { retro_run(); + if (getenv("D3DH_EVERY") && api == 12 && frame12 != NULL) + { + UINT pitch, y, x; + unsigned long sum = 2166136261ul; + const unsigned char *rows = copy_out12(frame12, iface12.required_state, frame_w, frame_h, &pitch); + for (y = 0; rows != NULL && y < frame_h; y++) + for (x = 0; x < frame_w * 4; x++) + sum = ((sum ^ rows[(size_t)y * pitch + x]) * 16777619ul) & 0xfffffffful; + printf("frame %u %ux%u %08lx\n", n, frame_w, frame_h, sum); + } + } if (getenv("D3DH_REFUSED")) { diff --git a/tools/rend/d3d_parse_arena.sh b/tools/rend/d3d_parse_arena.sh new file mode 100644 index 00000000..c5fb3cfa --- /dev/null +++ b/tools/rend/d3d_parse_arena.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# A frame's vertices, indices and modifier volumes' triangles parsed +# straight into memory the card reads (core/rend/dx12/parse_arena_d3d12.h), +# not copied into the upload ring after, with Direct3D 12: the core's +# Windows build under wine (vkd3d on lavapipe, whose adapter's memory is +# the processor's: CacheCoherentUMA), on a frontend that runs the core's +# frames as RetroArch does (tools/hdr/d3d_hdr.c). Every render the CPU +# parses is drawn from where it was parsed, and every frame is the one of +# the lists copied (D3DH_EVERY), as an adapter of its own has it (D3DH_NOT_UMA) - the +# test discs' plain one, heavy one (a volume far off the screen among it), +# DMA one and the one with modifier volumes, with each renderer, threaded +# rendering on and off. +# +# sh tools/rend/d3d_parse_arena.sh [core.dll] +# +# KEEP=1: the pictures and logs are kept, in $TMPDIR/d3d_parse_arena.. +# +# Needs wine, Xvfb, mingw-w64 and a Vulkan driver (lavapipe: +# VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json). +set -e +cd "$(dirname "$0")/../.." +CORE=$(readlink -f "${1:-parallel_dc_libretro.dll}") +WORK=$(readlink -f "${TMPDIR:-/tmp}")/d3d_parse_arena.$$ +mkdir -p "$WORK" +DISPLAY_NO=${D3D_PARSE_ARENA_DISPLAY:-:94} +Xvfb $DISPLAY_NO -screen 0 640x480x24 >/dev/null 2>&1 & +XVFB=$! +trap 'kill $XVFB 2>/dev/null; [ -n "${KEEP:-}" ] || rm -rf "$WORK"' EXIT +${CC_WIN:-x86_64-w64-mingw32-gcc} -O1 -Wall -I./core/libretro-common/include -I./core/deps/khronos \ + -o "$WORK/d3d_hdr.exe" tools/hdr/d3d_hdr.c -ld3d11 -ld3d12 -ldxguid +# (a directory a disc: their tracks' names are the same) +for disc in plain heavy dma cull; do + mkdir -p "$WORK/$disc" + case $disc in + plain) python3 tools/threads/live_disc.py "$WORK/$disc/test.gdi" ;; + *) python3 tools/threads/live_disc.py "$WORK/$disc/test.gdi" --$disc ;; + esac +done +fails=0 + +# run : 100 frames, from nothing kept by an +# earlier run (the discs write to the memory card) +run() { + run_disc=$1; run_name=$2; shift 2 + rm -rf "$WORK/dir"; mkdir -p "$WORK/dir" + env -u TMPDIR DISPLAY=$DISPLAY_NO WINEDEBUG=-all WINE_D3D_CONFIG=renderer=vulkan VKD3D_CONFIG=virtual_heaps \ + D3DH_EVERY=1 D3DH_VERBOSE=1 D3DH_API=12 D3DH_FRONTEND=sdr D3DH_DIR="Z:$WORK/dir" WINEDLLOVERRIDES="winedbg.exe=d" \ + timeout 600 wine "$WORK/d3d_hdr.exe" "Z:$CORE" "Z:$WORK/$run_disc/test.gdi" "Z:$WORK/$run_name.raw" 100 "$@" \ + > "$WORK/$run_name.log" 2>&1 || { echo " FAIL: the $run_name run failed:"; tail -5 "$WORK/$run_name.log"; return 1; } +} + +for disc in plain heavy dma cull; do + for r in per-triangle per-pixel; do + case $r in + per-triangle) set -- "reicast_alpha_sorting=per-triangle (normal)" ;; + per-pixel) set -- "reicast_alpha_sorting=per-pixel (accurate)" ;; + esac + for threaded in enabled disabled; do + name="$disc-$r-$threaded" + echo "$name" + run $disc "$name" "$@" reicast_threaded_rendering=$threaded || { fails=$((fails + 1)); continue; } + D3DH_NOT_UMA=1 run $disc "$name-copied" "$@" reicast_threaded_rendering=$threaded \ + || { fails=$((fails + 1)); continue; } + if ! grep -q "renders parsed where the card reads them" "$WORK/$name.log" \ + || grep -q "a render copied to the card" "$WORK/$name.log"; then + echo " FAIL: not every render parsed where the card reads it" + fails=$((fails + 1)) + fi + if grep -q "renders parsed where the card reads them" "$WORK/$name-copied.log"; then + echo " FAIL: with an adapter of its own, the parse is still put there" + fails=$((fails + 1)) + fi + if ! cmp -s "$WORK/$name.raw" "$WORK/$name-copied.raw" \ + || [ "$(grep '^frame ' "$WORK/$name.log")" != "$(grep '^frame ' "$WORK/$name-copied.log")" ]; then + echo " FAIL: the frames are not those of the lists copied" + fails=$((fails + 1)) + fi + done + done +done + +if [ $fails != 0 ]; then + echo "d3d_parse_arena: $fails failed" + exit 1 +fi +echo "d3d_parse_arena: all passed"