HDR: ACES 2.0's tables off the frame; SSAA and the X scaler exact

ACES 2.0's tables (the JMh parameters, the tone scale's LUT and the gamut
scales) were made on the frame that changed the peak, the gamut or the
surround. They are now made by the HDR worker, like the cube, through
the same lock-free hand-over: a slot for each kind of job taken by
atomic exchange, a CAS-pushed list of what is done, a stop flag for each
job, the worker parked on an eventcount. Nothing waits for it. Until the
new tables are done the encode keeps the old ones, and their cube, with
the gamut they were made for; before any, the tone scale alone
(HDR_DT_ACES2). A fill takes back what the worker finished before it
reads the tables, so a block is never half of the old tables and half of
the new ones. ACES2_InitSurround() is the reentrant init the worker
calls. hdr_dt_finish() builds in place, for the tests. Without threads
(TARGET_NO_THREADS) the build is done in place as before.

With the write-out off, what SSAA resolved is now encoded from the exact
average of its samples, not from the byte the picture rounded it to, and
is not dithered. Where something was drawn over the picture after the
resolve (the VMUs, the crosshairs), the picture is encoded as before:
the encode checks that the picture is the samples' resolve by the shared
rule (pvr_resolve_join()).
 - Vulkan per-triangle and per-strip: the HDR subpass reads the samples
   as a second input attachment.
 - Vulkan per-pixel: the last pass also stores each pixel's channel sums
   (OIT_FINAL_FRAG_S1_SUMS, an R16G16B16A16_UINT image in the OIT
   buffers' set), which the encode samples.
 - Vulkan compute: the walk stores the sums too, under a specialisation
   constant, which is 0 at 24-bit. It does the same for the X scaler's
   pairs of pixels, at one sample a pixel as well. Renders to a texture
   have walks of their own that store none.
 - Direct3D 12 per-triangle: the encode reads the multisampled target
   (PS_SSAA, hdr_encode_dxbc.inc).
The sums are scaled to 16 samples, so one encode pipeline takes any
count. Direct3D 12's compute kernels are unchanged: kernels_dx12.sh
leaves the PVRC_SUMS blocks out of their source, so their DXIL and sums
are as they were. At 24-bit none of this runs.

tools/vulkan/vk_hdr.sh gains --exact cases on two new discs built from
live_prog.c (live_disc.py --slant and --hscale). The first has a slanted
shaded triangle and is run on every renderer at 4x. The second adds the
X scaler into a 32-bit framebuffer and runs the compute renderer at one
sample a pixel. The old code fails both: it dithers, at most 1.08 of a
step off. tools/hdr/hdr_d3d.sh 12ssaa checks Direct3D 12's encode with
samples, with an overdrawn strip. tools/hdr/hdr_cube.sh checks the
tables: the first fill and a gamut change keep what there is, and both
converge on the tables made in place, and no block between is
a mix of two sets of tables. vk_sanitize.sh adds the per-pixel
and compute renderers with SSAA at scRGB.
This commit is contained in:
libretroadmin committed 2026-10-25 00:13:22 +02:00
1 parent 192c743256
commit e7cde6e176
45 files changed
+21835 -12356

No files matched your search

+12 -2
View File
@@ -1122,6 +1122,10 @@ void DX12Renderer::EndScreen(bool writeOutPass)
FlushBatch();
ScreenTarget& target = screenTargets[screenIndex];
const bool ssaa = SsaaActive();
/* the HDR output takes the samples' exact average where the picture is
* their resolve (not on a frame the chip's write-out changes after,
* nor where the per-pixel renderer resolved them itself): kept till then */
const bool hdrSamples = ssaa && BindsScreen() && context->HdrActive() && !(writeOutPass && writeOutPictures);
if (ssaa)
{
/* The samples resolved into the picture, all of it. Their contents
@@ -1141,7 +1145,7 @@ void DX12Renderer::EndScreen(bool writeOutPass)
context->SetScissor(whole);
SsaaResolve(ssaaColor[0].srv);
}
for (u32 i = 0; i < SsaaColorImages() && i < 2; i++)
for (u32 i = hdrSamples ? 1 : 0; i < SsaaColorImages() && i < 2; i++)
{
context->Transition(ssaaColor[i], D3D12_RESOURCE_STATE_RENDER_TARGET);
context->Cmd()->DiscardResource(ssaaColor[i].resource, nullptr);
@@ -1166,8 +1170,14 @@ void DX12Renderer::EndScreen(bool writeOutPass)
}
context->SetTargets(~0u, ~0u);
// as the frontend wants it - or encoded for its HDR output, which it is handed instead - and off to the GPU
if (!context->EncodeHdr(target.image, target.image.width, target.image.height))
if (!context->EncodeHdr(target.image, target.image.width, target.image.height,
hdrSamples ? &ssaaColor[0] : nullptr, ssaaLog2))
context->Transition(target.image, context->RequiredState());
if (hdrSamples)
{
context->Transition(ssaaColor[0], D3D12_RESOURCE_STATE_RENDER_TARGET);
context->Cmd()->DiscardResource(ssaaColor[0].resource, nullptr);
}
context->Submit();
havePicture = true;
+4 -1
View File
@@ -783,14 +783,17 @@ void DX12Context::SetTargets(u32 rtv, u32 dsv, u32 samples)
/* ---- The frame ---- */
bool DX12Context::EncodeHdr(DX12Image& image, u32 width, u32 height)
bool DX12Context::EncodeHdr(DX12Image& image, u32 width, u32 height, DX12Image *samples, u32 samplesLog2)
{
hdrFrame = nullptr;
if (hdr == nullptr || !image.resource)
return false;
Transition(image, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
if (samples != nullptr)
Transition(*samples, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
// (signalled by the Submit() that follows)
if (!hdr_d3d12_encode(hdr, Cmd(), &hdr_output, image.resource, DX12FrameFormat, width, height, true,
samples != nullptr ? samples->resource.get() : nullptr, samples != nullptr ? samplesLog2 : 0,
GetPresentIndex(), fenceValue + 1, &hdrFrame))
return false;
// what the encode bound is not what this has noted
+5 -2
View File
@@ -322,8 +322,11 @@ public:
* @height, encoded into the command list for the frontend's frame and
* left in the pixel shader resource state; the next SetFrame() hands
* the encoded one over. False without the HDR output: the picture is
* then to be left in required_state, as ever. */
bool EncodeHdr(DX12Image& image, u32 width, u32 height);
* then to be left in required_state, as ever. With SSAA's samples the
* picture was resolved from (@samples, 2^@samplesLog2 a pixel), where
* the picture is their resolve their exact average is encoded. */
bool EncodeHdr(DX12Image& image, u32 width, u32 height, DX12Image *samples = nullptr, u32 samplesLog2 = 0);
bool HdrActive() const { return hdr != nullptr; }
bool IsHandedOver(const DX12Image *image) const { return v2 && handedOver && image != nullptr && image->resource.get() == frameResource.get(); }
/* Just before video_refresh(RETRO_HW_FRAME_BUFFER_VALID) */
void Present();
+36 -11
View File
@@ -66,14 +66,18 @@ static const double Y_b = 20.0;
static double surround_1 = 0.59;
static double surround_2 = 0.9;
void ACES2_SetSurround( int mode ) {
static void ACES2_SurroundPair( int mode, double *c, double *F ) {
switch ( mode ) {
case 0: surround_1 = 0.525; surround_2 = 0.8; break; /* dark */
case 2: surround_1 = 0.69; surround_2 = 1.0; break; /* average */
default: surround_1 = 0.59; surround_2 = 0.9; break; /* dim */
case 0: *c = 0.525; *F = 0.8; break; /* dark */
case 2: *c = 0.69; *F = 1.0; break; /* average */
default: *c = 0.59; *F = 0.9; break; /* dim */
}
}
void ACES2_SetSurround( int mode ) {
ACES2_SurroundPair( mode, &surround_1, &surround_2 );
}
static const double J_scale = 100.0;
static const double cam_nl_Y_reference = 100.0;
static const double cam_nl_offset = 0.2713 * cam_nl_Y_reference;
@@ -234,7 +238,16 @@ RGB into the cone response space and back, and the scalars the lightness
conversion needs.
================
*/
static void ACES2_InitJMh( const double prims[8], double surround_c, double surround_F,
aces2JMhParams_t *p );
void ACES2_InitJMhParams( const double prims[8], aces2JMhParams_t *p ) {
ACES2_InitJMh( prims, surround_1, surround_2, p );
}
/* (the surround given, not the one ACES2_SetSurround() keeps) */
static void ACES2_InitJMh( const double prims[8], double surround_c, double surround_F,
aces2JMhParams_t *p ) {
double MATRIX_16[9], RGB_TO_XYZ[9], XYZ_w[3], RGB_w[3];
double D_RGB[3], RGB_wc[3], RGB_Aw[3];
double cone_to_Aab[9];
@@ -250,7 +263,7 @@ void ACES2_InitJMhParams( const double prims[8], aces2JMhParams_t *p ) {
int i;
white[0] = white[1] = white[2] = ref_luminance;
model_gamma = surround_1 * ( 1.48 + sqrt( Y_b / ref_luminance ) );
model_gamma = surround_c * ( 1.48 + sqrt( Y_b / ref_luminance ) );
{
double XYZ_to_RGB_cam[9];
@@ -299,8 +312,8 @@ void ACES2_InitJMhParams( const double prims[8], aces2JMhParams_t *p ) {
for ( i = 0; i < 3; i++ ) {
p->cone_to_Aab[i*3+0] = cone_to_Aab[i*3+0] / A_w;
p->cone_to_Aab[i*3+1] = cone_to_Aab[i*3+1] * 43.0 * surround_2;
p->cone_to_Aab[i*3+2] = cone_to_Aab[i*3+2] * 43.0 * surround_2;
p->cone_to_Aab[i*3+1] = cone_to_Aab[i*3+1] * 43.0 * surround_F;
p->cone_to_Aab[i*3+2] = cone_to_Aab[i*3+2] * 43.0 * surround_F;
}
mat3_invert( p->cone_to_Aab, p->Aab_to_cone );
@@ -1187,11 +1200,12 @@ double ACES2_GammaInvForHue( const aces2GammaTable_t *table, double hue ) {
static const double ACES2_AP0[8] = { 0.7347, 0.2653, 0.0, 1.0, 0.0001, -0.077, 0.32168, 0.33767 };
static const double ACES2_AP1[8] = { 0.713, 0.293, 0.165, 0.830, 0.128, 0.044, 0.32168, 0.33767 };
void ACES2_Init( const double limitPrims[8], double peakLuminance, aces2Params_t *p ) {
static void ACES2_InitWith( const double limitPrims[8], double peakLuminance,
double surround_c, double surround_F, aces2Params_t *p ) {
p->peakLuminance = peakLuminance;
ACES2_InitJMhParams( ACES2_AP0, &p->input );
ACES2_InitJMhParams( ACES2_AP1, &p->reach );
ACES2_InitJMhParams( limitPrims, &p->limit );
ACES2_InitJMh( ACES2_AP0, surround_c, surround_F, &p->input );
ACES2_InitJMh( ACES2_AP1, surround_c, surround_F, &p->reach );
ACES2_InitJMh( limitPrims, surround_c, surround_F, &p->limit );
ACES2_InitTSParams( peakLuminance, &p->ts );
ACES2_InitChromaParams( &p->input, &p->reach, &p->ts, peakLuminance, &p->chroma );
ACES2_InitGamutParams( &p->input, &p->ts, peakLuminance, &p->gamut );
@@ -1199,6 +1213,17 @@ void ACES2_Init( const double limitPrims[8], double peakLuminance, aces2Params_t
ACES2_BuildUpperHullGammaTable( &p->cusp, &p->gamut, &p->limit, peakLuminance, &p->gamma );
}
void ACES2_Init( const double limitPrims[8], double peakLuminance, aces2Params_t *p ) {
ACES2_InitWith( limitPrims, peakLuminance, surround_1, surround_2, p );
}
void ACES2_InitSurround( const double limitPrims[8], double peakLuminance, int surround,
aces2Params_t *p ) {
double c, F;
ACES2_SurroundPair( surround, &c, &F );
ACES2_InitWith( limitPrims, peakLuminance, c, F, p );
}
/*
================
ACES2_ClampAP0ToAP1
+4
View File
@@ -251,6 +251,10 @@ typedef struct {
} aces2Params_t;
void ACES2_Init( const double limitPrims[8], double peakLuminance, aces2Params_t *p );
/* The same with @surround (as ACES2_SetSurround()'s mode) rather than the
* surround set: touches nothing but @p, so it may run on any thread */
void ACES2_InitSurround( const double limitPrims[8], double peakLuminance, int surround,
aces2Params_t *p );
void ACES2_OutputTransformFwd( const double aces[3], const aces2Params_t *p, double RGB[3] );
/*
+37 -1
View File
@@ -101,6 +101,10 @@ cbuffer Params : register(b1)
int sdrEotf;
int scrgb;
int deband;
int ssaaLog2; /* HDR_ENCODE_SSAA: the samples', log2 */
int pad0;
int pad1;
int pad2;
};
Texture2D<float4> picture : register(t0);
@@ -108,6 +112,22 @@ Texture2D<float4> uDtAces2Lut : register(t1);
Texture2D<float4> uDtFilmLut : register(t2);
Texture2D<float4> uDtFilmDesat : register(t3);
Texture3D<float4> uDtAces2Cube : register(t4);
#ifdef HDR_ENCODE_SSAA
/* With SSAA (after rend/tile/pvr_lane.h and pvr_resolve.h), the samples
* the picture was resolved from, read again: where the picture is their
* resolve (pvr_resolve_join(), the rule the resolve wrote it by) their
* exact average is encoded, not the byte it was rounded to; where
* something was drawn over the picture since (the VMUs, the crosshairs),
* the picture. As hdr_encode.frag's with SSAA. */
Texture2DMS<float4> samples : register(t5);
/* (a unorm8 colour's bytes, red lowest, as its picture holds them: exact) */
uint hdr_pack_unorm8(float4 v)
{
const uint4 b = uint4(round(saturate(v) * 255.0));
return b.r | (b.g << 8) | (b.b << 16) | (b.a << 24);
}
#endif
SamplerState linearClamp : register(s0);
SamplerState linearWrap : register(s1);
@@ -688,7 +708,23 @@ float3 hdr_deband(float3 rgb, float2 fragcoord)
float4 main(float4 pos : SV_Position) : SV_Target
{
float3 rgb = picture.Load(int3((int)pos.x, (int)pos.y, 0)).rgb;
const float4 pic = picture.Load(int3((int)pos.x, (int)pos.y, 0));
float3 rgb = pic.rgb;
#ifdef HDR_ENCODE_SSAA
{
uint rb = 0u;
uint ga = 0u;
for (uint s = 0u; s < (1u << (uint)ssaaLog2); s++)
{
const uint c = hdr_pack_unorm8(samples.Load(int2(pos.xy), s));
rb += pvr_resolve_rb(c);
ga += pvr_resolve_ga(c);
}
if (hdr_pack_unorm8(pic) == pvr_resolve_join(rb, ga, (uint)ssaaLog2))
rgb = float3((float)(rb & 0xffffu), (float)(ga & 0xffffu), (float)(rb >> 16))
/ (255.0 * (float)(1u << (uint)ssaaLog2));
}
#endif
if (deband != 0)
rgb = hdr_deband(rgb, pos.xy);
return float4(encode_hdr_output(rgb, paperWhite, peak, expandGamut, shoulder, sdrEotf,
+35 -10
View File
@@ -30,7 +30,7 @@
* and the tables (t0-t4) - b0 in the table rather than at the root, since
* vkd3d cannot have a root CBV and static samplers both on drivers with
* eight descriptor sets (lavapipe's) */
#define HDR_D3D12_SRVS 6
#define HDR_D3D12_SRVS 7 /* b0; t0 the picture, t1-t4 the tables, t5 SSAA's samples */
static const IID iid_root_signature = { 0xc54a6b66, 0x72df, 0x4ee8, { 0x8b, 0xe5, 0xa9, 0x46, 0xa1, 0x42, 0x92, 0x14 } };
static const IID iid_pipeline_state = { 0x765a30f3, 0xf624, 0x4c6f, { 0xa8, 0x28, 0xac, 0xe9, 0x48, 0x62, 0x24, 0x45 } };
@@ -73,6 +73,7 @@ struct hdr_d3d12
D3D12_RESOURCE_STATES handed;
ID3D12RootSignature *root;
ID3D12PipelineState *pso;
ID3D12PipelineState *pso_ssaa; /* with SSAA's samples (hdr_encode.hlsl's HDR_ENCODE_SSAA) */
ID3D12DescriptorHeap *srv_heap; /* shader visible: HDR_D3D12_SRVS a slot */
ID3D12DescriptorHeap *rtv_heap; /* one an output */
UINT srv_step;
@@ -274,6 +275,19 @@ static void tables_update(hdr_d3d12_t *e, ID3D12GraphicsCommandList *cmd)
e->tables[T_DESAT] = table_make(e, cmd, desat, HDR_DT_DESAT_WIDTH, HDR_DT_DESAT_HEIGHT, 1, 4, false, false);
}
/* SSAA's samples at @place of the heap: @res, multisampled, or a null view of one */
static void write_srv_ms(hdr_d3d12_t *e, unsigned place, ID3D12Resource *res, DXGI_FORMAT format)
{
D3D12_SHADER_RESOURCE_VIEW_DESC desc;
D3D12_CPU_DESCRIPTOR_HANDLE h = heap_cpu(e->srv_heap);
h.ptr += (SIZE_T)place * e->srv_step;
memset(&desc, 0, sizeof(desc));
desc.Format = format;
desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
ID3D12Device_CreateShaderResourceView(e->device, res, &desc, h);
}
static void write_srv(hdr_d3d12_t *e, unsigned place, ID3D12Resource *res,
DXGI_FORMAT format, bool three_d)
{
@@ -384,15 +398,15 @@ static bool make_root(hdr_d3d12_t *e)
return ok;
}
static bool make_pso(hdr_d3d12_t *e)
static bool make_pso(hdr_d3d12_t *e, const unsigned char *ps, size_t ps_size, ID3D12PipelineState **out)
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC desc;
memset(&desc, 0, sizeof(desc));
desc.pRootSignature = e->root;
desc.VS.pShaderBytecode = hdr_encode_dxbc_VS;
desc.VS.BytecodeLength = sizeof(hdr_encode_dxbc_VS);
desc.PS.pShaderBytecode = hdr_encode_dxbc_PS;
desc.PS.BytecodeLength = sizeof(hdr_encode_dxbc_PS);
desc.PS.pShaderBytecode = ps;
desc.PS.BytecodeLength = ps_size;
desc.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
desc.SampleMask = 0xffffffffu;
desc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
@@ -403,7 +417,7 @@ static bool make_pso(hdr_d3d12_t *e)
desc.RTVFormats[0] = e->format;
desc.SampleDesc.Count = 1;
return SUCCEEDED(ID3D12Device_CreateGraphicsPipelineState(e->device, &desc,
&iid_pipeline_state, (void **)&e->pso));
&iid_pipeline_state, (void **)out));
}
static ID3D12DescriptorHeap *make_heap(ID3D12Device *device, D3D12_DESCRIPTOR_HEAP_TYPE type,
@@ -446,7 +460,9 @@ hdr_d3d12_t *hdr_d3d12_new(ID3D12Device *device, DXGI_FORMAT format,
e->rtv_step = ID3D12Device_GetDescriptorHandleIncrementSize(device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
none.Begin = 0;
none.End = 0;
if ( !make_root(e) || !make_pso(e)
if ( !make_root(e)
|| !make_pso(e, hdr_encode_dxbc_PS, sizeof(hdr_encode_dxbc_PS), &e->pso)
|| !make_pso(e, hdr_encode_dxbc_PS_SSAA, sizeof(hdr_encode_dxbc_PS_SSAA), &e->pso_ssaa)
|| !(e->srv_heap = make_heap(device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
HDR_D3D12_SLOTS * HDR_D3D12_SRVS, true))
|| !(e->rtv_heap = make_heap(device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, HDR_D3D12_MAX_OUTPUTS, false))
@@ -476,6 +492,7 @@ void hdr_d3d12_free(hdr_d3d12_t *e)
RELEASE(ID3D12DescriptorHeap, e->rtv_heap);
RELEASE(ID3D12DescriptorHeap, e->srv_heap);
RELEASE(ID3D12PipelineState, e->pso);
RELEASE(ID3D12PipelineState, e->pso_ssaa);
RELEASE(ID3D12RootSignature, e->root);
RELEASE(ID3D12Fence, e->fence);
RELEASE(ID3D12Device, e->device);
@@ -485,8 +502,8 @@ void hdr_d3d12_free(hdr_d3d12_t *e)
bool hdr_d3d12_encode(hdr_d3d12_t *e, ID3D12GraphicsCommandList *cmd,
const struct hdr_output *out, ID3D12Resource *picture,
DXGI_FORMAT picture_format, unsigned width, unsigned height,
bool unorm8, unsigned index, uint64_t signal,
ID3D12Resource **texture)
bool unorm8, ID3D12Resource *samples, unsigned samples_log2,
unsigned index, uint64_t signal, ID3D12Resource **texture)
{
hdr_d3d12_slot_t *s;
hdr_d3d12_output_t *o;
@@ -526,7 +543,14 @@ bool hdr_d3d12_encode(hdr_d3d12_t *e, ID3D12GraphicsCommandList *cmd,
/* the cube only once the texture of it is here, whatever the block says */
if (!e->tables[T_CUBE])
block->gm[2] = 0.0f;
hdr_encode_params_fill(&params, out, unorm8);
/* (the samples' exact average is not 8 bits a channel: no dither) */
if (samples && samples_log2)
{
hdr_encode_params_fill(&params, out, false);
params.ssaa_log2 = (int32_t)samples_log2;
}
else
hdr_encode_params_fill(&params, out, unorm8);
first = e->slot * HDR_D3D12_SRVS;
{
@@ -538,6 +562,7 @@ bool hdr_d3d12_encode(hdr_d3d12_t *e, ID3D12GraphicsCommandList *cmd,
ID3D12Device_CreateConstantBufferView(e->device, &cbv, h);
}
write_srv(e, first + 1, picture, picture_format, false);
write_srv_ms(e, first + 6, params.ssaa_log2 ? samples : NULL, picture_format);
for (i = 0; i < 4; i++)
{
unsigned t = i;
@@ -564,7 +589,7 @@ bool hdr_d3d12_encode(hdr_d3d12_t *e, ID3D12GraphicsCommandList *cmd,
barrier(cmd, o->texture, e->handed, D3D12_RESOURCE_STATE_RENDER_TARGET);
ID3D12GraphicsCommandList_SetDescriptorHeaps(cmd, 1, heaps);
ID3D12GraphicsCommandList_SetGraphicsRootSignature(cmd, e->root);
ID3D12GraphicsCommandList_SetPipelineState(cmd, e->pso);
ID3D12GraphicsCommandList_SetPipelineState(cmd, params.ssaa_log2 ? e->pso_ssaa : e->pso);
ID3D12GraphicsCommandList_SetGraphicsRoot32BitConstants(cmd, 0,
sizeof(params) / 4, &params, 0);
ID3D12GraphicsCommandList_SetGraphicsRootDescriptorTable(cmd, 1, table);
+9 -5
View File
@@ -33,14 +33,18 @@ void hdr_d3d12_free(hdr_d3d12_t *e);
* state, @picture_format, @width x @height, 8 bits a channel when @unorm8)
* encoded into the texture for the frontend's frame @index, which is left
* in the state the frontend wants; @signal is the value the fence is
* signalled with once @cmd is done. *@texture is that texture, the
* encoder's to keep. What @cmd had bound - heaps included - is not put
* back. False, nothing recorded, when it could not be. */
* signalled with once @cmd is done. With SSAA's samples the picture was
* resolved from (@samples, multisampled, of @picture_format, in the pixel
* shader resource state, 2^@samples_log2 a pixel; NULL and 0 without),
* where the picture is their resolve their exact average is encoded.
* *@texture is that texture, the encoder's to keep. What @cmd had bound -
* heaps included - is not put back. False, nothing recorded, when it could
* not be. */
bool hdr_d3d12_encode(hdr_d3d12_t *e, ID3D12GraphicsCommandList *cmd,
const struct hdr_output *out, ID3D12Resource *picture,
DXGI_FORMAT picture_format, unsigned width, unsigned height,
bool unorm8, unsigned index, uint64_t signal,
ID3D12Resource **texture);
bool unorm8, ID3D12Resource *samples, unsigned samples_log2,
unsigned index, uint64_t signal, ID3D12Resource **texture);
#ifdef __cplusplus
}
File diff suppressed because it is too large. Load diff
+2
View File
@@ -21,4 +21,6 @@ void hdr_encode_params_fill(struct hdr_encode_params *p,
p->sdr_eotf = o->sdr_eotf;
p->scrgb = o->format == HDR_OUTPUT_SCRGB;
p->deband = deband;
p->ssaa_log2 = 0;
p->pad[0] = p->pad[1] = p->pad[2] = 0;
}
+2
View File
@@ -48,6 +48,8 @@ struct hdr_encode_params
int32_t sdr_eotf;
int32_t scrgb;
int32_t deband; /* the picture is 8 bits a channel */
int32_t ssaa_log2; /* Direct3D 12: SSAA's samples the picture was resolved from, log2 (0: none) */
int32_t pad[3]; /* (a constant buffer is whole 16 bytes) */
};
void hdr_encode_params_fill(struct hdr_encode_params *p,
+327 -136
View File
@@ -23,9 +23,17 @@ static struct hdr_dt_settings dt_set = {
static unsigned dt_generation = 1;
/* ACES 2.0 (full)'s tables, as made from their key (dt_aces2_key_t):
* the reference's parameters, and the hue LUT and its scales the shader
* reads. dt_aces2 is dt_tables' parameters, or NULL while there are none. */
typedef struct dt_aces2_tables
{
aces2Params_t params;
uint16_t lut[HDR_DT_ACES2_LUT_WIDTH * 4];
double scales[4];
} dt_aces2_tables_t;
static dt_aces2_tables_t *dt_tables;
static aces2Params_t *dt_aces2;
static uint16_t dt_aces2_lut[HDR_DT_ACES2_LUT_WIDTH * 4];
static double dt_aces2_scales[4];
static float dt_aces2_peak;
static int dt_aces2_gamut = -1;
static int dt_aces2_surround = -1;
@@ -294,9 +302,9 @@ static bool dt_panel_prims(const uint16_t xy[8], double out[8])
return true;
}
static const double *dt_limit_prims(const uint16_t panel_xy[8], double scratch[8])
static const double *dt_limit_prims(int gamut, const uint16_t panel_xy[8], double scratch[8])
{
switch (dt_set.aces2_gamut)
switch (gamut)
{
case 0: return DT_PRIMS_709;
case 1: return DT_PRIMS_P3;
@@ -312,39 +320,92 @@ static const double *dt_limit_prims(const uint16_t panel_xy[8], double scratch[8
return DT_PRIMS_P3;
}
static bool dt_aces2_build(float peak, const uint16_t panel_xy[8])
/* What ACES 2.0 (full)'s tables are made from */
typedef struct dt_aces2_key
{
float peak;
int gamut;
int surround;
uint16_t xy[8];
} dt_aces2_key_t;
static void dt_aces2_key_now(float peak, const uint16_t panel_xy[8], dt_aces2_key_t *k)
{
k->peak = peak;
k->gamut = dt_set.aces2_gamut;
k->surround = dt_set.aces2_surround;
memcpy(k->xy, panel_xy, sizeof(k->xy));
}
/* Peak arrives every frame; a jittering last bit must not re-solve the
* searched tables. The panel's primaries count only with the display's
* gamut. */
static bool dt_aces2_key_same(const dt_aces2_key_t *a, const dt_aces2_key_t *b)
{
return fabs((double)a->peak - (double)b->peak) < 0.5
&& a->gamut == b->gamut && a->surround == b->surround
&& (a->gamut != 3 || memcmp(a->xy, b->xy, sizeof(a->xy)) == 0);
}
/* The tables @t (made from @k) made the ones in use */
static void dt_aces2_adopt(dt_aces2_tables_t *t, const dt_aces2_key_t *k)
{
free(dt_tables);
dt_tables = t;
dt_aces2 = &t->params;
dt_aces2_built++;
dt_aces2_peak = k->peak;
dt_aces2_gamut = k->gamut;
dt_aces2_surround = k->surround;
memcpy(dt_aces2_xy, k->xy, sizeof(dt_aces2_xy));
dt_generation++;
}
/* The tables for @k, from the limiting primaries @prims: the solve that
* costs a few milliseconds (ACES2_Init()'s searched tables). Touches only
* what it makes, so it may run on any thread. */
static dt_aces2_tables_t *dt_aces2_make(const double prims[8], const dt_aces2_key_t *k)
{
dt_aces2_tables_t *t = (dt_aces2_tables_t*)calloc(1, sizeof(*t));
if (!t)
return NULL;
ACES2_InitSurround(prims, (double)k->peak, k->surround, &t->params);
ACES2_PackHueTables(&t->params, t->lut, t->scales);
return t;
}
/* The tables there are already for @k */
static bool dt_aces2_current(const dt_aces2_key_t *k)
{
dt_aces2_key_t have;
if (!dt_tables)
return false;
have.peak = dt_aces2_peak;
have.gamut = dt_aces2_gamut;
have.surround = dt_aces2_surround;
memcpy(have.xy, dt_aces2_xy, sizeof(have.xy));
return dt_aces2_key_same(&have, k);
}
/* The tables for @k made here, now */
static bool dt_aces2_build_here(const dt_aces2_key_t *k)
{
double scratch[8];
const double *prims;
bool same_xy = (memcmp(dt_aces2_xy, panel_xy, sizeof(dt_aces2_xy)) == 0);
/* Peak arrives every frame; a jittering last bit must not re-solve
* the searched tables. */
if (dt_aces2 && fabs((double)dt_aces2_peak - (double)peak) < 0.5
&& dt_aces2_gamut == dt_set.aces2_gamut
&& dt_aces2_surround == dt_set.aces2_surround
&& (dt_set.aces2_gamut != 3 || same_xy))
dt_aces2_tables_t *t;
if (dt_aces2_current(k))
return true;
if (!dt_aces2)
{
dt_aces2 = (aces2Params_t*)calloc(1, sizeof(*dt_aces2));
if (!dt_aces2)
return false;
}
prims = dt_limit_prims(panel_xy, scratch);
ACES2_SetSurround(dt_set.aces2_surround);
ACES2_Init(prims, (double)peak, dt_aces2);
ACES2_PackHueTables(dt_aces2, dt_aces2_lut, dt_aces2_scales);
dt_aces2_built++;
dt_aces2_peak = peak;
dt_aces2_gamut = dt_set.aces2_gamut;
dt_aces2_surround = dt_set.aces2_surround;
memcpy(dt_aces2_xy, panel_xy, sizeof(dt_aces2_xy));
dt_generation++;
t = dt_aces2_make(dt_limit_prims(k->gamut, k->xy, scratch), k);
if (!t)
return dt_tables != NULL;
dt_aces2_adopt(t, k);
return true;
}
/* The tables for @peak and the panel's primaries; with threads, made by
* the worker (below) while those there are go on being used. True while
* there are any. */
static bool dt_aces2_build(float peak, const uint16_t panel_xy[8]);
/* A CTL 3x3 (row vector times matrix, i.e. GL column-major) into std140. */
static void dt_mat3(float dst[12], const double m[9])
{
@@ -519,81 +580,124 @@ static bool dt_cube_bake_here(unsigned rows, float paper)
}
#ifndef TARGET_NO_THREADS
/* The cube is baked on a worker of its own, so that no frame pays for it:
* the frame thread hands it a job - a copy of the tables and the paper
* white, and a cube to fill - and takes back what it is done with, and
* neither ever waits for the other. All of it is atomics: the job handed
* over is one pointer (exchanged), what comes back a list pushed to with
* a CAS, a superseded job told to stop by a flag of its own. The worker
* parks on an eventcount between jobs. Only the frame thread allocates,
* frees and reads the cubes; the worker writes the one cube it holds.
/* ACES 2.0 (full)'s tables and its cube are made on a worker of its own,
* so that no frame pays for them: the frame thread hands it a job - for
* the tables, the primaries, peak and surround to solve them for; for the
* cube, a copy of the tables and the paper white, and a cube to fill -
* and takes back what it is done with, and neither ever waits for the
* other. Until new tables are back the ones there are go on being used,
* and their cube with them; with none yet, the tone scale alone is drawn.
* All of it is atomics: a job handed over is one pointer for each kind
* (exchanged), what comes back a list pushed to with a CAS, a superseded
* job told to stop by a flag of its own. The worker parks on an
* eventcount between jobs. Only the frame thread allocates, frees and
* reads what is made; the worker writes only the job it holds.
* hdr_dt_free() stops it and joins it, outside any frame. */
typedef struct dt_cube_job
enum { DT_JOB_TABLES = 0, DT_JOB_CUBE, DT_JOB_KINDS };
typedef struct dt_job
{
int kind;
retro_atomic_int_t stop; /* superseded: give up (the cube) */
int done; /* the worker's, read once the job is back */
struct dt_job *next; /* on the list of jobs back */
/* the tables: what they are for, made into tables */
dt_aces2_key_t key;
double prims[8];
dt_aces2_tables_t *tables;
/* the cube: from a copy of the tables, at paper */
aces2Params_t params;
float paper;
uint16_t *cube;
retro_atomic_int_t stop; /* superseded: give up */
int baked; /* the worker's, read once the job is back */
struct dt_cube_job *next; /* on the list of jobs back */
} dt_cube_job_t;
} dt_job_t;
static struct
{
sthread_t *thread;
retro_eventcount_t work; /* the worker parks here */
retro_atomic_ptr_t want; /* the job handed over, not yet taken */
retro_atomic_ptr_t back; /* the jobs it is done with, newest first */
retro_eventcount_t work; /* the worker parks here */
retro_atomic_ptr_t want[DT_JOB_KINDS]; /* a job handed over, not yet taken */
retro_atomic_ptr_t back; /* the jobs it is done with, newest first */
retro_atomic_int_t quit;
dt_cube_job_t *current; /* the frame thread's: the job for the cube wanted */
dt_job_t *current[DT_JOB_KINDS]; /* the frame thread's: the job for what is wanted */
bool ready;
bool failed; /* no worker could be had: baked here */
bool failed; /* no worker could be had: made here */
} dt_baker;
static void dt_job_free(dt_cube_job_t *job)
static void dt_job_free(dt_job_t *job)
{
if (!job)
return;
free(job->tables);
free(job->cube);
free(job);
}
static void dt_baker_thread(void *unused)
static void dt_job_run(dt_job_t *job)
{
const unsigned n = HDR_DT_ACES2_CUBE;
unsigned row;
if (job->kind == DT_JOB_TABLES)
{
job->tables = dt_aces2_make(job->prims, &job->key);
job->done = job->tables != NULL;
return;
}
for (row = 0; row < n * n; row++)
{
if (retro_atomic_load_relaxed_int(&job->stop)
|| retro_atomic_load_relaxed_int(&dt_baker.quit))
break;
dt_cube_row(job->cube, row, job->paper, &job->params);
}
job->done = row == n * n;
}
static dt_job_t *dt_baker_take(void)
{
int k;
for (k = 0; k < DT_JOB_KINDS; k++)
{
dt_job_t *job = (dt_job_t*)retro_atomic_exchange_ptr(&dt_baker.want[k], NULL);
if (job)
return job;
}
return NULL;
}
static bool dt_baker_wanted(void)
{
int k;
for (k = 0; k < DT_JOB_KINDS; k++)
if (retro_atomic_load_acquire_ptr(&dt_baker.want[k]) != NULL)
return true;
return false;
}
static void dt_baker_thread(void *unused)
{
(void)unused;
for (;;)
{
dt_cube_job_t *job = (dt_cube_job_t*)retro_atomic_exchange_ptr(&dt_baker.want, NULL);
/* (the tables first: the cube is made from them) */
dt_job_t *job = dt_baker_take();
if (!job)
{
int key;
if (retro_atomic_load_acquire_int(&dt_baker.quit))
return;
key = retro_eventcount_prepare_wait(&dt_baker.work);
if (retro_atomic_load_acquire_ptr(&dt_baker.want) != NULL
|| retro_atomic_load_acquire_int(&dt_baker.quit))
if (dt_baker_wanted() || retro_atomic_load_acquire_int(&dt_baker.quit))
retro_eventcount_cancel_wait(&dt_baker.work);
else
retro_eventcount_commit_wait(&dt_baker.work, key);
continue;
}
{
unsigned row;
for (row = 0; row < n * n; row++)
{
if (retro_atomic_load_relaxed_int(&job->stop)
|| retro_atomic_load_relaxed_int(&dt_baker.quit))
break;
dt_cube_row(job->cube, row, job->paper, &job->params);
}
job->baked = row == n * n;
}
/* back, the cube with it (release: the frame thread reads the cube) */
dt_job_run(job);
/* back, with what it made (release: the frame thread reads it) */
for (;;)
{
void *head = retro_atomic_load_relaxed_ptr(&dt_baker.back);
job->next = (dt_cube_job_t*)head;
job->next = (dt_job_t*)head;
if (retro_atomic_cas_ptr(&dt_baker.back, head, job))
break;
}
@@ -602,6 +706,7 @@ static void dt_baker_thread(void *unused)
static bool dt_baker_start(void)
{
int k;
if (dt_baker.thread)
return true;
if (dt_baker.failed)
@@ -613,7 +718,8 @@ static bool dt_baker_start(void)
dt_baker.failed = true;
return false;
}
retro_atomic_ptr_init(&dt_baker.want, NULL);
for (k = 0; k < DT_JOB_KINDS; k++)
retro_atomic_ptr_init(&dt_baker.want[k], NULL);
retro_atomic_ptr_init(&dt_baker.back, NULL);
dt_baker.ready = true;
}
@@ -624,88 +730,133 @@ static bool dt_baker_start(void)
return dt_baker.thread != NULL;
}
/* The job for the cube wanted is no longer wanted */
static void dt_baker_drop(void)
/* @job, made, handed to the worker as what is wanted of its kind */
static void dt_baker_submit(dt_job_t *job)
{
dt_cube_job_t *job = dt_baker.current;
dt_cube_job_t *untaken;
dt_baker.current = NULL;
retro_atomic_int_init(&job->stop, 0);
dt_baker.current[job->kind] = job;
/* (empty: a job left in it was dropped and taken back, or taken) */
dt_job_free((dt_job_t*)retro_atomic_exchange_ptr(&dt_baker.want[job->kind], job));
retro_eventcount_notify(&dt_baker.work);
}
/* The job of @kind for what is wanted is no longer wanted */
static void dt_baker_drop(int kind)
{
dt_job_t *job = dt_baker.current[kind];
dt_job_t *untaken;
dt_baker.current[kind] = NULL;
if (!job)
return;
retro_atomic_store_release_int(&job->stop, 1);
/* still not taken: the worker never sees it */
untaken = (dt_cube_job_t*)retro_atomic_load_acquire_ptr(&dt_baker.want);
if (untaken == job && retro_atomic_cas_ptr(&dt_baker.want, job, NULL))
untaken = (dt_job_t*)retro_atomic_load_acquire_ptr(&dt_baker.want[kind]);
if (untaken == job && retro_atomic_cas_ptr(&dt_baker.want[kind], job, NULL))
dt_job_free(job);
/* else it comes back, and is freed then */
}
/* What the worker is done with: the cube wanted made the one in use, the
* rest freed. True when the cube wanted is complete. */
static bool dt_baker_collect(void)
/* What the worker is done with: the tables and the cube wanted made the
* ones in use, the rest freed */
static void dt_baker_collect(void)
{
const unsigned n = HDR_DT_ACES2_CUBE;
dt_cube_job_t *job = (dt_cube_job_t*)retro_atomic_exchange_ptr(&dt_baker.back, NULL);
bool complete = dt_cube_rows == n * n;
dt_job_t *job;
if (!dt_baker.ready)
return;
job = (dt_job_t*)retro_atomic_exchange_ptr(&dt_baker.back, NULL);
while (job)
{
dt_cube_job_t *next = job->next;
if (job == dt_baker.current)
dt_job_t *next = job->next;
if (job == dt_baker.current[job->kind])
{
dt_baker.current = NULL;
if (job->baked)
dt_baker.current[job->kind] = NULL;
if (job->done && job->kind == DT_JOB_TABLES)
{
dt_aces2_adopt(job->tables, &job->key);
job->tables = NULL;
}
else if (job->done)
{
free(dt_cube);
dt_cube = job->cube;
job->cube = NULL;
dt_cube_rows = n * n;
dt_generation++;
complete = true;
}
}
dt_job_free(job);
job = next;
}
return complete;
}
static bool dt_aces2_build(float peak, const uint16_t panel_xy[8])
{
dt_aces2_key_t k;
dt_job_t *job;
dt_aces2_key_now(peak, panel_xy, &k);
dt_baker_collect();
if (dt_aces2_current(&k))
{
/* (a job for others, wanted before these came back, goes) */
dt_baker_drop(DT_JOB_TABLES);
return true;
}
if (!dt_baker_start())
return dt_aces2_build_here(&k);
job = dt_baker.current[DT_JOB_TABLES];
if (job && dt_aces2_key_same(&job->key, &k))
return dt_tables != NULL;
dt_baker_drop(DT_JOB_TABLES);
job = (dt_job_t*)calloc(1, sizeof(*job));
if (!job)
return dt_tables != NULL;
job->kind = DT_JOB_TABLES;
job->key = k;
{
double scratch[8];
memcpy(job->prims, dt_limit_prims(k.gamut, k.xy, scratch), sizeof(job->prims));
}
dt_baker_submit(job);
return dt_tables != NULL;
}
/* true once the cube is complete; never waits */
static bool dt_cube_bake(unsigned rows, float paper)
{
const unsigned n = HDR_DT_ACES2_CUBE;
dt_job_t *job;
if (!dt_aces2)
return false;
dt_baker_collect();
if (dt_cube_rekey(paper))
dt_baker_drop();
dt_baker_drop(DT_JOB_CUBE);
if (!dt_baker_start())
return dt_cube_bake_here(rows, paper);
if (dt_baker_collect())
if (dt_cube_rows == n * n)
return true;
if (!dt_baker.current)
if (dt_baker.current[DT_JOB_CUBE])
return false;
job = (dt_job_t*)calloc(1, sizeof(*job));
if (!job)
return false;
job->cube = (uint16_t*)malloc(sizeof(uint16_t) * 4 * n * n * n);
if (!job->cube)
{
dt_cube_job_t *job = (dt_cube_job_t*)calloc(1, sizeof(*job));
if (!job)
return false;
job->cube = (uint16_t*)malloc(sizeof(uint16_t) * 4 * n * n * n);
if (!job->cube)
{
free(job);
return false;
}
job->params = *dt_aces2;
job->paper = paper;
retro_atomic_int_init(&job->stop, 0);
dt_baker.current = job;
/* (want is empty: a job left in it was dropped and taken back above) */
dt_job_free((dt_cube_job_t*)retro_atomic_exchange_ptr(&dt_baker.want, job));
retro_eventcount_notify(&dt_baker.work);
free(job);
return false;
}
job->kind = DT_JOB_CUBE;
job->params = *dt_aces2;
job->paper = paper;
dt_baker_submit(job);
return false;
}
/* The worker stopped and joined, and all it held freed: outside a frame */
static void dt_baker_free(void)
{
int k;
if (dt_baker.thread)
{
retro_atomic_store_release_int(&dt_baker.quit, 1);
@@ -715,23 +866,31 @@ static void dt_baker_free(void)
}
if (dt_baker.ready)
{
dt_job_free((dt_cube_job_t*)retro_atomic_exchange_ptr(&dt_baker.want, NULL));
dt_job_t *job;
for (k = 0; k < DT_JOB_KINDS; k++)
dt_job_free((dt_job_t*)retro_atomic_exchange_ptr(&dt_baker.want[k], NULL));
job = (dt_job_t*)retro_atomic_exchange_ptr(&dt_baker.back, NULL);
while (job)
{
dt_cube_job_t *job = (dt_cube_job_t*)retro_atomic_exchange_ptr(&dt_baker.back, NULL);
while (job)
{
dt_cube_job_t *next = job->next;
dt_job_free(job);
job = next;
}
dt_job_t *next = job->next;
dt_job_free(job);
job = next;
}
retro_eventcount_free(&dt_baker.work);
dt_baker.ready = false;
}
dt_baker.current = NULL;
dt_baker.failed = false;
for (k = 0; k < DT_JOB_KINDS; k++)
dt_baker.current[k] = NULL;
dt_baker.failed = false;
}
#else
static bool dt_aces2_build(float peak, const uint16_t panel_xy[8])
{
dt_aces2_key_t k;
dt_aces2_key_now(peak, panel_xy, &k);
return dt_aces2_build_here(&k);
}
static bool dt_cube_bake(unsigned rows, float paper)
{
return dt_cube_bake_here(rows, paper);
@@ -743,10 +902,10 @@ void hdr_dt_aces2_cube_finish(float paper_white_nits)
if (!dt_aces2)
return;
#ifndef TARGET_NO_THREADS
if (dt_cube_rekey(paper_white_nits))
dt_baker_drop();
dt_baker_collect();
dt_cube_rekey(paper_white_nits);
if (dt_cube_rows < HDR_DT_ACES2_CUBE * HDR_DT_ACES2_CUBE)
dt_baker_drop();
dt_baker_drop(DT_JOB_CUBE);
#endif
dt_cube_bake_here(HDR_DT_ACES2_CUBE * HDR_DT_ACES2_CUBE, paper_white_nits);
}
@@ -782,26 +941,52 @@ bool hdr_dt_enabled(void)
return dt_set.curve != HDR_DT_KNEE;
}
void hdr_dt_fill(struct hdr_dt_block *b, float paper_white_nits,
float peak_nits, float panel_peak_nits, const uint16_t panel_xy[8])
/* The peak the curves go to: the frontend's, the panel's own or the set */
static float dt_peak(float peak_nits, float panel_peak_nits)
{
int curve = dt_set.curve;
float paper = paper_white_nits > 0.0f ? paper_white_nits : 200.0f;
float peak = peak_nits;
float H, A, slope, film[2];
double S0, S1, C2;
memset(b, 0, sizeof(*b));
float peak = peak_nits;
if (dt_set.peak_mode == 1 && panel_peak_nits > 0.0f)
peak = panel_peak_nits;
else if (dt_set.peak_mode == 2 && dt_set.peak_nits > 0.0f)
peak = dt_set.peak_nits;
if (!(peak > 0.0f))
peak = 1000.0f;
return peak;
}
void hdr_dt_finish(float paper_white_nits, float peak_nits,
float panel_peak_nits, const uint16_t panel_xy[8], bool cube)
{
dt_aces2_key_t k;
if (dt_set.curve != HDR_DT_ACES2FULL)
return;
dt_aces2_key_now(dt_peak(peak_nits, panel_peak_nits), panel_xy, &k);
#ifndef TARGET_NO_THREADS
dt_baker_collect();
if (!dt_aces2_current(&k))
dt_baker_drop(DT_JOB_TABLES);
#endif
dt_aces2_build_here(&k);
if (cube)
hdr_dt_aces2_cube_finish(paper_white_nits > 0.0f ? paper_white_nits : 200.0f);
}
void hdr_dt_fill(struct hdr_dt_block *b, float paper_white_nits,
float peak_nits, float panel_peak_nits, const uint16_t panel_xy[8])
{
int curve = dt_set.curve;
float paper = paper_white_nits > 0.0f ? paper_white_nits : 200.0f;
float peak = dt_peak(peak_nits, panel_peak_nits);
float H, A, slope, film[2];
double S0, S1, C2;
memset(b, 0, sizeof(*b));
H = peak / paper;
if (H < 1.0f)
H = 1.0f;
/* (with threads the tables are made by the worker, and those there
* are drawn with until they are: with none yet, the tone scale alone) */
if (curve == HDR_DT_ACES2FULL && !dt_aces2_build(peak, panel_xy))
curve = HDR_DT_ACES2; /* no tables: the tone scale alone */
@@ -832,14 +1017,23 @@ void hdr_dt_fill(struct hdr_dt_block *b, float paper_white_nits,
if (curve == HDR_DT_ACES2FULL)
{
const aces2Params_t *p = dt_aces2;
const aces2Params_t *p;
double scratch[8], m[9];
int i;
/* Once the cube is baked the shader reads it instead of solving the
* transform per pixel. Until then, it is baked (on a worker of its
* own where there are threads). First: what it takes back from the
* worker can be new tables, which free the ones before, so the
* block is of those there are after it. */
b->gm[2] = dt_cube_bake(HDR_DT_CUBE_ROWS_PER_FILL, paper) ? 1.0f : 0.0f;
p = dt_aces2;
dt_mat3(b->in_rgb_to_cam, p->input.RGB_to_CAM16_c);
dt_mat3(b->in_cone_to_aab, p->input.cone_to_Aab);
dt_mat3(b->lim_aab_to_cone, p->limit.Aab_to_cone);
dt_mat3(b->lim_cam_to_rgb, p->limit.CAM16_c_to_RGB);
if (!ACES2_PrimariesMatrix(dt_limit_prims(panel_xy, scratch), DT_PRIMS_2020, m))
/* (the gamut the tables in use are for, which until the worker's are
* back may not be the one set) */
if (!ACES2_PrimariesMatrix(dt_limit_prims(dt_aces2_gamut, dt_aces2_xy, scratch), DT_PRIMS_2020, m))
for (i = 0; i < 9; i++)
m[i] = (i % 4 == 0) ? 1.0 : 0.0;
dt_mat3(b->aces2_to_out, m);
@@ -865,18 +1059,14 @@ void hdr_dt_fill(struct hdr_dt_block *b, float paper_white_nits,
b->cc2[3] = (float)p->gamut.focus_dist;
b->gm[0] = (float)p->gamut.lower_hull_gamma_inv;
b->gm[1] = (float)p->ts.forward_limit;
/* Once the cube is baked the shader reads it instead of solving the
* transform per pixel. Until then, it is baked (on a worker
* of its own where there are threads). */
b->gm[2] = dt_cube_bake(HDR_DT_CUBE_ROWS_PER_FILL, paper) ? 1.0f : 0.0f;
for (i = 0; i < 4; i++)
b->lut_scale[i] = (float)dt_aces2_scales[i];
b->lut_scale[i] = (float)dt_tables->scales[i];
}
}
const uint16_t *hdr_dt_aces2_lut(void)
{
return dt_aces2 ? dt_aces2_lut : NULL;
return dt_tables ? dt_tables->lut : NULL;
}
const uint16_t *hdr_dt_film_lut(void)
@@ -1001,7 +1191,8 @@ void hdr_dt_free(void)
free(dt_cube);
dt_cube = NULL;
dt_cube_rows = 0;
free(dt_aces2);
free(dt_tables);
dt_tables = NULL;
dt_aces2 = NULL;
dt_aces2_gamut = -1;
dt_aces2_surround = -1;
+5
View File
@@ -117,6 +117,11 @@ unsigned hdr_dt_generation(void);
const uint16_t *hdr_dt_aces2_cube(void);
/* Bake the rest of the cube now; for tests. */
void hdr_dt_aces2_cube_finish(float paper_white_nits);
/* ACES 2.0 (full)'s tables for these, made now if they are not there (and
* with @cube, its cube too), where the fills leave them to the worker;
* for tests, which want what they draw now. Nothing for other curves. */
void hdr_dt_finish(float paper_white_nits, float peak_nits,
float panel_peak_nits, const uint16_t panel_xy[8], bool cube);
/* The reference the cube is baked from: what dt_aces2() computes, in
* double precision. Scene Rec.709 (1.0 = paper white) in, the limiting
* primaries (1.0 = 100 nits) out. Valid once hdr_dt_fill ran with
+36
View File
@@ -430,6 +430,14 @@ layout(constant_id = 1) const uint PVRC_LOG2N = 0u;
layout(constant_id = 2) const uint PVRC_KEEP = 4u;
layout(constant_id = 3) const uint PVRC_TEST = 0u;
layout(constant_id = 4) const uint PVRC_VARIANT = 0u;
#if defined(PVRC_SUMS)
/* Vulkan's walk, with the HDR output: each pixel's channel sums stored
* too, scaled to 16 samples (vulkan/hdr_encode_vk.h's HDR_VK_SUMS_LOG2),
* for the encode to have the exact average the picture was rounded from.
* (Not in the Direct3D 12 kernels: tools/rend/kernels_dx12.sh leaves
* these blocks out.) */
layout(constant_id = 5) const uint PVRC_SUMS_ON = 0u;
#endif /* PVRC_SUMS */
#if PVRC_KERNEL == PVRC_K_GPUSRC
layout(std430, set = 0, binding = 0) coherent buffer Frame { uint w[]; } frm;
@@ -1443,6 +1451,9 @@ layout(local_size_x = 256) in;
#if defined(PVRC_SHADER_GLSL)
layout(set = 0, binding = 3, rgba8) uniform writeonly image2D outImage;
#if defined(PVRC_SUMS)
layout(set = 0, binding = 13, rgba16ui) uniform writeonly uimage2D sumsImage;
#endif /* PVRC_SUMS */
layout(set = 0, binding = 4) uniform sampler2D lastPicture;
layout(set = 0, binding = 5) uniform sampler2D palette;
layout(set = 0, binding = 6) uniform sampler2D textures[PVRC_TEX_SLOTS];
@@ -3124,6 +3135,9 @@ void main()
uint c;
ivec2 at = ivec2(g_px, g_py);
bool stores = smp == 0u && g_in;
#if defined(PVRC_SUMS)
uint sums_rb = pvr_resolve_rb(g_col), sums_ga = pvr_resolve_ga(g_col), sums_log2 = PVRC_LOG2N;
#endif /* PVRC_SUMS */
if ((frm.w[PVRC_F_FLAGS] & PVRC_FF_HSCALE) != 0u)
{
uint rb = pvr_resolve_rb(g_col), ga = pvr_resolve_ga(g_col);
@@ -3140,6 +3154,11 @@ void main()
rb += orb;
}
c = pvr_resolve_join(rb, ga, PVRC_LOG2N + 1u);
#if defined(PVRC_SUMS)
sums_rb = rb;
sums_ga = ga;
sums_log2 = PVRC_LOG2N + 1u;
#endif /* PVRC_SUMS */
at.x = g_px >> 1;
stores = stores && (g_px & 1) == 0;
}
@@ -3149,6 +3168,23 @@ void main()
* it rounds or truncates, and however it divides, gives the byte) */
if (stores)
imageStore(outImage, at, (vec4(float(c & 255u), float((c >> 8) & 255u), float((c >> 16) & 255u), float(c >> 24)) + 0.25) / 255.0);
#if defined(PVRC_SUMS)
if (PVRC_SUMS_ON != 0u)
{
/* (the lanes of a pixel's samples summed as pvr_resolve_lanes()
* does, where the X scaler did not sum them above) */
if ((frm.w[PVRC_F_FLAGS] & PVRC_FF_HSCALE) == 0u)
for (uint m = 1u; m < PVRC_SAMPLES; m <<= 1)
{
sums_rb += LANE_SHUFFLE_XOR(sums_rb, m);
sums_ga += LANE_SHUFFLE_XOR(sums_ga, m);
}
sums_rb <<= 4u - sums_log2;
sums_ga <<= 4u - sums_log2;
if (stores)
imageStore(sumsImage, at, uvec4(sums_rb & 0xffffu, sums_ga & 0xffffu, sums_rb >> 16, sums_ga >> 16));
}
#endif /* PVRC_SUMS */
if (g_tid == 0u && peels > 1u)
atomicMax(bin.w[PVRC_B_STATS(pvrc_runs()) + PVRC_ST_PEELS], peels);
if (g_tid == 0u && g_hiz_skipped != 0u)
+70 -23
View File
@@ -28,10 +28,10 @@ static const int WalkUnsorted = PVRC_KERNELS + 1;
* frame's every primitive (4 MiB) */
static const u32 PoolEntries = 2u << 20;
// The bindings of the kernels' one set
enum { B_FRAME, B_REC, B_BIN, B_OUT, B_LAST, B_PAL, B_TEX, B_VTX, B_IDX, B_MOD, B_CMD, B_TRD, B_DFRM, B_COUNT };
// The bindings of the kernels' one set (B_SUMS: the HDR output's channel sums, pvr_compute.h's PVRC_SUMS)
enum { B_FRAME, B_REC, B_BIN, B_OUT, B_LAST, B_PAL, B_TEX, B_VTX, B_IDX, B_MOD, B_CMD, B_TRD, B_DFRM, B_SUMS, B_COUNT };
// (the buffers a frame of the GPU's TA decode is read from: B_VTX to B_DFRM)
static const int GpuBindings = B_COUNT - B_VTX;
static const int GpuBindings = B_SUMS - B_VTX;
// what PVRC_K_GPUSRC reads of the decode's buffers
static_assert(PVRC_TAG_C_WORDS == TAG_C_WORDS && PVRC_TAG_C_COUNT == TAG_C_COUNT && PVRC_TAG_C_FIRST == TAG_C_FIRST
@@ -110,8 +110,17 @@ struct ComputeDrawer::Resources
vk::UniqueDescriptorSetLayout setLayout;
vk::UniquePipelineLayout pipelineLayout;
vk::UniquePipeline pipelines[PVRC_KERNELS + 2]; // (and the walk's unsorted variant last: WalkUnsorted)
// with SSAA, the kernels for renders to a texture, at one sample a pixel
/* with SSAA, the kernels for renders to a texture, at one sample a
* pixel; with the HDR output, the walks for them (which store no sums:
* the screen's do) - null where they are the screen's */
vk::UniquePipeline rttPipelines[PVRC_KERNELS + 2];
/* With the HDR output, each pixel's channel sums, which the screen's
* walk stores (hdr_encode_vk.h: of HDR_VK_SUMS_SAMPLES); otherwise an
* image of one pixel, for the binding */
std::unique_ptr<FramebufferAttachment> sums;
bool sumsOn = false;
bool sumsLayout = false; // in the general layout
bool sumsStored = false; // by the frame just recorded (and nothing over them since)
vk::UniqueDescriptorPool descriptorPool;
std::vector<vk::DescriptorSet> sets;
// what each set's texture slots, picture and last picture have
@@ -215,6 +224,17 @@ bool ComputeDrawer::Init(vk::Extent2D extent, u32 samples, CommandPool *pool, Sa
return fail(e.what());
}
/* (with the HDR output, the walk's channel sums: what was resolved,
* SSAA's samples and the X scaler's pairs, has more than 8 bits) */
r->sumsOn = context->HdrSumsReady();
try {
r->sums = std::unique_ptr<FramebufferAttachment>(new FramebufferAttachment(context->GetPhysicalDevice(), device));
r->sums->Init(r->sumsOn ? extent.width : 1, r->sumsOn ? extent.height : 1, vk::Format::eR16G16B16A16Uint,
vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eSampled, false);
} catch (const vk::SystemError& e) {
return fail(e.what());
}
vk::DescriptorSetLayoutBinding bindings[B_COUNT] = {
{ B_FRAME, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute },
{ B_REC, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute },
@@ -229,6 +249,7 @@ bool ComputeDrawer::Init(vk::Extent2D extent, u32 samples, CommandPool *pool, Sa
{ B_CMD, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute },
{ B_TRD, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute },
{ B_DFRM, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute },
{ B_SUMS, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute },
};
r->setLayout = device.createDescriptorSetLayoutUnique(vk::DescriptorSetLayoutCreateInfo(vk::DescriptorSetLayoutCreateFlags(), B_COUNT, bindings));
r->pipelineLayout = device.createPipelineLayoutUnique(vk::PipelineLayoutCreateInfo(vk::PipelineLayoutCreateFlags(), 1, &r->setLayout.get()));
@@ -236,17 +257,22 @@ bool ComputeDrawer::Init(vk::Extent2D extent, u32 samples, CommandPool *pool, Sa
/* every kernel, now, for this many samples, and for one when that is
* not one (renders to a texture): none is made while a game runs. The
* walk in its two variants (pvr_compute.h: PVRC_V_*), the one for the
* tiles without sorted translucent fragments keeping none */
for (int set = 0; set < (this->samples > 1 ? 2 : 1); set++)
* tiles without sorted translucent fragments keeping none. (With the
* HDR output, the screen's walks store the channel sums; at one sample
* a pixel, renders to a texture have walks of their own, which do not.) */
for (int set = 0; set < (this->samples > 1 || r->sumsOn ? 2 : 1); set++)
{
const u32 n = set == 0 ? this->samples : 1;
for (int k = 1; k <= WalkUnsorted; k++)
{
const bool walk = k == PVRC_K_WALK || k == WalkUnsorted;
const u32 spec[5] = { n, SSAA_LOG2(n), k == WalkUnsorted ? 1u : r->keep, 0,
k == WalkUnsorted ? PVRC_V_UNSORTED : k == PVRC_K_WALK ? PVRC_V_SORTED : PVRC_V_ALL };
const vk::SpecializationMapEntry entries[5] = { { 0, 0, 4 }, { 1, 4, 4 }, { 2, 8, 4 }, { 3, 12, 4 }, { 4, 16, 4 } };
const vk::SpecializationInfo specInfo(5, entries, sizeof(spec), spec);
if (set == 1 && this->samples <= 1 && !walk)
continue;
const u32 spec[6] = { n, SSAA_LOG2(n), k == WalkUnsorted ? 1u : r->keep, 0,
k == WalkUnsorted ? PVRC_V_UNSORTED : k == PVRC_K_WALK ? PVRC_V_SORTED : PVRC_V_ALL,
set == 0 && r->sumsOn ? 1u : 0u };
const vk::SpecializationMapEntry entries[6] = { { 0, 0, 4 }, { 1, 4, 4 }, { 2, 8, 4 }, { 3, 12, 4 }, { 4, 16, 4 }, { 5, 20, 4 } };
const vk::SpecializationInfo specInfo(6, entries, sizeof(spec), spec);
// (the walk's textures taken in turns where the device cannot index them non-uniformly)
const bool waterfall = walk && !context->SupportsNonUniformTextureIndexing();
vk::UniqueShaderModule module = device.createShaderModuleUnique(vk::ShaderModuleCreateInfo(vk::ShaderModuleCreateFlags(),
@@ -260,7 +286,7 @@ bool ComputeDrawer::Init(vk::Extent2D extent, u32 samples, CommandPool *pool, Sa
const vk::DescriptorPoolSize sizes[] = {
{ vk::DescriptorType::eStorageBuffer, (3u + GpuBindings) * slots },
{ vk::DescriptorType::eStorageImage, (u32)slots },
{ vk::DescriptorType::eStorageImage, 2u * slots },
{ vk::DescriptorType::eCombinedImageSampler, (PVRC_TEX_SLOTS + 2u) * slots },
};
r->descriptorPool = device.createDescriptorPoolUnique(vk::DescriptorPoolCreateInfo(vk::DescriptorPoolCreateFlags(), slots, 3, sizes));
@@ -281,6 +307,7 @@ bool ComputeDrawer::Init(vk::Extent2D extent, u32 samples, CommandPool *pool, Sa
std::vector<vk::DescriptorBufferInfo> buffers;
buffers.reserve((3 + GpuBindings) * slots);
std::vector<vk::WriteDescriptorSet> writes;
const vk::DescriptorImageInfo sumsInfo(vk::Sampler(), r->sums->GetImageView(), vk::ImageLayout::eGeneral);
for (int s = 0; s < slots; s++)
{
buffers.push_back(vk::DescriptorBufferInfo(*r->ring->buffer, (vk::DeviceSize)s * r->ringWords * 4, (vk::DeviceSize)r->ringWords * 4));
@@ -290,11 +317,12 @@ bool ComputeDrawer::Init(vk::Extent2D extent, u32 samples, CommandPool *pool, Sa
buffers.push_back(vk::DescriptorBufferInfo(*r->bins->buffer, 0, VK_WHOLE_SIZE));
writes.push_back(vk::WriteDescriptorSet(r->sets[s], B_BIN, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr, &buffers.back()));
// (a frame of the parse's reads its vertices, indices and volumes from its own words; Prepare() changes them for the decode's)
for (int b = B_VTX; b < B_COUNT; b++)
for (int b = B_VTX; b < B_SUMS; b++)
{
buffers.push_back(vk::DescriptorBufferInfo(*r->ring->buffer, (vk::DeviceSize)s * r->ringWords * 4, (vk::DeviceSize)r->ringWords * 4));
writes.push_back(vk::WriteDescriptorSet(r->sets[s], b, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr, &buffers.back()));
}
writes.push_back(vk::WriteDescriptorSet(r->sets[s], B_SUMS, 0, 1, vk::DescriptorType::eStorageImage, &sumsInfo));
}
{
std::array<VkBuffer, GpuBindings> ring;
@@ -335,6 +363,11 @@ void ComputeDrawer::Term()
memset(statWhy, 0, sizeof(statWhy));
}
vk::ImageView ComputeDrawer::Sums() const
{
return res && res->sumsStored ? res->sums->GetImageView() : vk::ImageView();
}
bool ComputeDrawer::CanDraw() const
{
if (!ready || pvrrc.isRenderFramebuffer || (pvrrc.gpu && gpuDraw == nullptr))
@@ -507,16 +540,26 @@ void ComputeDrawer::Record(vk::Image image, vk::ImageView view, vk::ImageView la
* render pass, make them ready for fragment shaders, not these) */
const vk::MemoryBarrier toCompute(vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eIndirectCommandRead);
const vk::ImageMemoryBarrier toGeneral(vk::AccessFlags(), vk::AccessFlagBits::eShaderWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eGeneral,
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, image, vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1));
const vk::ImageMemoryBarrier toGeneral[2] = {
vk::ImageMemoryBarrier(vk::AccessFlags(), vk::AccessFlagBits::eShaderWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eGeneral,
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, image, vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)),
// (the sums, the first time: general from then on)
vk::ImageMemoryBarrier(vk::AccessFlags(), vk::AccessFlagBits::eShaderWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eGeneral,
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, r.sums->GetImage(), vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)),
};
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eDrawIndirect, vk::DependencyFlags(), toCompute, nullptr, toGeneral);
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eDrawIndirect, vk::DependencyFlags(), 1, &toCompute, 0, nullptr,
r.sumsLayout ? 1 : 2, toGeneral);
r.sumsLayout = true;
cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *r.pipelineLayout, 0, 1, &set, 0, nullptr);
// (a render to a texture is drawn at one sample a pixel)
const vk::UniquePipeline *pipelines = r.rtt && samples > 1 ? r.rttPipelines : r.pipelines;
/* (a render to a texture is drawn at one sample a pixel, and stores no
* sums: the kernels of its own, where there are) */
auto pipeline = [&](int kernel) {
return r.rtt && r.rttPipelines[kernel] ? *r.rttPipelines[kernel] : *r.pipelines[kernel];
};
const u32 n = r.rtt ? 1 : samples;
auto step = [&](int kernel, u32 x, u32 y) {
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, *pipelines[kernel]);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline(kernel));
if (x != 0 && y != 0)
cmd.dispatch(x, y, 1);
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eDrawIndirect,
@@ -530,7 +573,7 @@ void ComputeDrawer::Record(vk::Image image, vk::ImageView view, vk::ImageView la
/* A frame of the decode's: its numbering, and the dispatches of
* the kernels whose sizes are the GPU's, from it */
auto indirect = [&](int kernel, u32 at) {
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, *pipelines[kernel]);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline(kernel));
cmd.dispatchIndirect(bins, (vk::DeviceSize)(PVRC_B_STATS(r.runs) + at) * 4);
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eDrawIndirect,
vk::DependencyFlags(), vk::MemoryBarrier(vk::AccessFlagBits::eShaderWrite,
@@ -551,9 +594,9 @@ void ComputeDrawer::Record(vk::Image image, vk::ImageView view, vk::ImageView la
}
/* (the walk's two variants, each over the coarse tiles of its list,
* the sort's: nothing between them) */
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, *pipelines[WalkUnsorted]);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline(WalkUnsorted));
cmd.dispatchIndirect(bins, (vk::DeviceSize)(PVRC_B_STATS(r.runs) + PVRC_ST_WALK_U_X) * 4);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, *pipelines[PVRC_K_WALK]);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline(PVRC_K_WALK));
cmd.dispatchIndirect(bins, (vk::DeviceSize)(PVRC_B_STATS(r.runs) + PVRC_ST_WALK_S_X) * 4);
step(PVRC_K_WALK, 0, 0);
/* the Y filter, in place over the picture, where the frame has it */
@@ -561,10 +604,14 @@ void ComputeDrawer::Record(vk::Image image, vk::ImageView view, vk::ImageView la
pvrc_yfilter_groups(words, &yx, &yy);
if (yy != 0)
{
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, *pipelines[PVRC_K_YFILTER]);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline(PVRC_K_YFILTER));
cmd.dispatch(yx, yy, 1);
}
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, *pipelines[PVRC_K_CLEAR]);
/* (the sums are the picture's where it was written by the walk alone:
* not where the write-out has been over it since; and what they add
* is more than 8 bits, which a pixel of one sample does not have) */
r.sumsStored = r.sumsOn && !r.rtt && yy == 0 && (n > 1 || (words[PVRC_F_FLAGS] & PVRC_FF_HSCALE) != 0);
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline(PVRC_K_CLEAR));
cmd.dispatchIndirect(bins, (vk::DeviceSize)(PVRC_B_STATS(r.runs) + PVRC_ST_CLEAR_X) * 4);
// the counts, for when this slot comes round
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(),
+4
View File
@@ -102,6 +102,10 @@ public:
* null). Its own command buffer of the pool's, submitted with the
* frame's others. */
void Record(vk::Image image, vk::ImageView view, vk::ImageView last);
/* With the HDR output, the channel sums of what the screen's frame just
* recorded resolved, for the encode (VulkanContext::HdrEncode()); null
* when it stored none, or they would add nothing */
vk::ImageView Sums() const;
// a frame left to the raster renderer, and why (ComputeFrame::Why_*)
void Fallback(u32 why) { statFallback++; statWhy[why < ComputeFrame::Why_Count ? why : 0]++; }
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -959,7 +959,7 @@ void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderMan
dependencies.size(), dependencies.data()));
}
if (hdr && !hdrPipeline)
hdrPipeline = GetContext()->HdrSubpassPipeline(*renderPass, samples > 1 ? 2 : 1);
hdrPipeline = GetContext()->HdrSubpassPipeline(*renderPass, samples > 1 ? 2 : 1, samples);
size_t size = VulkanContext::Instance()->GetSwapChainSize();
if (colorAttachments.size() > size)
{
@@ -1117,7 +1117,8 @@ void ScreenDrawer::EndRenderPass()
hdrAfter = writeOut.Runs(viewport.height);
if (!hdrAfter)
GetContext()->HdrSubpassDraw(*commandPool, currentCommandBuffer, *hdrPipeline,
colorAttachments[GetCurrentImage()]->GetImageView(), viewport);
colorAttachments[GetCurrentImage()]->GetImageView(),
samples > 1 ? samplesAttachment->GetImageView() : vk::ImageView(), viewport);
}
else if (hdrPipeline)
hdrAfter = true;
+110 -29
View File
@@ -31,6 +31,8 @@
#define B_FILM_LUT 10
#define B_DESAT 11
#define B_CUBE 12
/* with SSAA, the samples the picture was resolved from (hdr_encode.frag) */
#define B_SAMPLES 13
#define HOST_MEMORY (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
@@ -98,6 +100,9 @@ struct hdr_vk
VkPipeline pipeline; /* the pass of its own's */
VkShaderModule vert;
VkShaderModule frag_subpass;
VkShaderModule frag_subpass_ssaa;
VkShaderModule frag_sums;
VkPipeline pipeline_sums; /* the pass of its own's, with the channel sums (hdr_vk_sums_ready()) */
VkDescriptorPool descriptor_pool;
VkSampler nearest;
VkSampler linear_clamp;
@@ -472,7 +477,7 @@ static bool make_render_pass(hdr_vk_t *e)
static bool make_layouts(hdr_vk_t *e)
{
static const uint32_t tables[] = { B_ACES2_LUT, B_FILM_LUT, B_DESAT, B_CUBE };
VkDescriptorSetLayoutBinding b[6];
VkDescriptorSetLayoutBinding b[7];
VkDescriptorSetLayoutCreateInfo set;
VkPushConstantRange push;
VkPipelineLayoutCreateInfo layout;
@@ -494,14 +499,16 @@ static bool make_layouts(hdr_vk_t *e)
}
b[5].binding = B_DT;
b[5].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
for (i = 0; i < 6; i++)
b[6].binding = B_SAMPLES;
b[6].descriptorType = b[0].descriptorType;
for (i = 0; i < 7; i++)
{
b[i].descriptorCount = 1;
b[i].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
}
memset(&set, 0, sizeof(set));
set.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
set.bindingCount = 6;
set.bindingCount = 7;
set.pBindings = b;
if (vkCreateDescriptorSetLayout(e->device, &set, NULL, &e->set_layout[k]) != VK_SUCCESS)
return false;
@@ -532,7 +539,7 @@ static VkShaderModule make_module(VkDevice device, const uint32_t *code, size_t
/* The triangle over the picture into colour attachment 0 of @subpass */
static VkPipeline make_pipeline(hdr_vk_t *e, VkShaderModule frag, VkPipelineLayout layout,
VkRenderPass render_pass, uint32_t subpass)
VkRenderPass render_pass, uint32_t subpass, const VkSpecializationInfo *spec)
{
static const VkDynamicState dynamic_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineShaderStageCreateInfo stages[2];
@@ -556,6 +563,7 @@ static VkPipeline make_pipeline(hdr_vk_t *e, VkShaderModule frag, VkPipelineLayo
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
stages[1].module = frag;
stages[1].pName = "main";
stages[1].pSpecializationInfo = spec;
memset(&vertex, 0, sizeof(vertex));
vertex.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
memset(&assembly, 0, sizeof(assembly));
@@ -613,9 +621,9 @@ static bool make_slots(hdr_vk_t *e)
unsigned r, i;
sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
sizes[0].descriptorCount = sets * 9;
sizes[0].descriptorCount = sets * 10;
sizes[1].type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
sizes[1].descriptorCount = sets;
sizes[1].descriptorCount = sets * 2;
sizes[2].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
sizes[2].descriptorCount = sets * 2;
memset(&pool, 0, sizeof(pool));
@@ -672,12 +680,16 @@ hdr_vk_t *hdr_vk_new(const struct retro_hw_render_interface_vulkan *iface,
e->linear_wrap = make_sampler(e->device, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT);
e->vert = make_module(e->device, code->vert, code->vert_size);
e->frag_subpass = make_module(e->device, code->frag_subpass, code->frag_subpass_size);
e->frag_subpass_ssaa = make_module(e->device, code->frag_subpass_ssaa, code->frag_subpass_ssaa_size);
e->frag_sums = make_module(e->device, code->frag_sums, code->frag_sums_size);
frag = make_module(e->device, code->frag, code->frag_size);
if ( e->nearest != VK_NULL_HANDLE && e->linear_clamp != VK_NULL_HANDLE
&& e->linear_wrap != VK_NULL_HANDLE && e->vert != VK_NULL_HANDLE
&& e->frag_subpass != VK_NULL_HANDLE && frag != VK_NULL_HANDLE
&& e->frag_subpass != VK_NULL_HANDLE && e->frag_subpass_ssaa != VK_NULL_HANDLE
&& e->frag_sums != VK_NULL_HANDLE
&& frag != VK_NULL_HANDLE
&& make_render_pass(e) && make_layouts(e))
e->pipeline = make_pipeline(e, frag, e->pipeline_layout[K_SAMPLED], e->render_pass, 0);
e->pipeline = make_pipeline(e, frag, e->pipeline_layout[K_SAMPLED], e->render_pass, 0, NULL);
if (frag != VK_NULL_HANDLE)
vkDestroyShaderModule(e->device, frag, NULL);
if (e->pipeline == VK_NULL_HANDLE || !make_slots(e))
@@ -707,6 +719,8 @@ void hdr_vk_free(hdr_vk_t *e)
vkDestroyDescriptorPool(e->device, e->descriptor_pool, NULL);
if (e->pipeline != VK_NULL_HANDLE)
vkDestroyPipeline(e->device, e->pipeline, NULL);
if (e->pipeline_sums != VK_NULL_HANDLE)
vkDestroyPipeline(e->device, e->pipeline_sums, NULL);
for (k = 0; k < K_COUNT; k++)
{
if (e->pipeline_layout[k] != VK_NULL_HANDLE)
@@ -718,6 +732,10 @@ void hdr_vk_free(hdr_vk_t *e)
vkDestroyShaderModule(e->device, e->vert, NULL);
if (e->frag_subpass != VK_NULL_HANDLE)
vkDestroyShaderModule(e->device, e->frag_subpass, NULL);
if (e->frag_subpass_ssaa != VK_NULL_HANDLE)
vkDestroyShaderModule(e->device, e->frag_subpass_ssaa, NULL);
if (e->frag_sums != VK_NULL_HANDLE)
vkDestroyShaderModule(e->device, e->frag_sums, NULL);
if (e->render_pass != VK_NULL_HANDLE)
vkDestroyRenderPass(e->device, e->render_pass, NULL);
if (e->nearest != VK_NULL_HANDLE)
@@ -765,13 +783,15 @@ void hdr_vk_prepare(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer c
block->gm[2] = 0.0f;
}
/* @samples (or SSAA's sums), in @samples_layout */
static void write_set(hdr_vk_t *e, hdr_vk_slot_t *s, VkDescriptorSet set,
VkDescriptorType picture_type, VkImageView picture)
VkDescriptorType picture_type, VkImageView picture, VkImageView samples,
VkImageLayout samples_layout)
{
VkDescriptorImageInfo images[5];
VkDescriptorImageInfo images[6];
VkDescriptorBufferInfo dt;
VkWriteDescriptorSet w[6];
static const uint32_t bindings[5] = { B_PICTURE, B_ACES2_LUT, B_FILM_LUT, B_DESAT, B_CUBE };
VkWriteDescriptorSet w[7];
static const uint32_t bindings[6] = { B_PICTURE, B_ACES2_LUT, B_FILM_LUT, B_DESAT, B_CUBE, B_SAMPLES };
unsigned i;
images[0].sampler = picture_type == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT ? VK_NULL_HANDLE : e->nearest;
@@ -784,27 +804,32 @@ static void write_set(hdr_vk_t *e, hdr_vk_slot_t *s, VkDescriptorSet set,
images[3].imageView = table_view(e, T_DESAT);
images[4].sampler = e->linear_clamp;
images[4].imageView = table_view(e, T_CUBE);
/* (the picture where there are no samples: there for the layout, unread) */
images[5].sampler = images[0].sampler;
images[5].imageView = samples != VK_NULL_HANDLE ? samples : picture;
images[5].imageLayout = samples != VK_NULL_HANDLE ? samples_layout : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
dt.buffer = s->dt.buffer;
dt.offset = 0;
dt.range = sizeof(struct hdr_dt_block);
memset(w, 0, sizeof(w));
for (i = 0; i < 5; i++)
for (i = 0; i < 6; i++)
{
images[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
if (i < 5)
images[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
w[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
w[i].dstSet = set;
w[i].dstBinding = bindings[i];
w[i].descriptorCount = 1;
w[i].descriptorType = i == 0 ? picture_type : VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
w[i].descriptorType = (i == 0 || i == 5) ? picture_type : VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
w[i].pImageInfo = &images[i];
}
w[5].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
w[5].dstSet = set;
w[5].dstBinding = B_DT;
w[5].descriptorCount = 1;
w[5].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
w[5].pBufferInfo = &dt;
vkUpdateDescriptorSets(e->device, 6, w, 0, NULL);
w[6].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
w[6].dstSet = set;
w[6].dstBinding = B_DT;
w[6].descriptorCount = 1;
w[6].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
w[6].pBufferInfo = &dt;
vkUpdateDescriptorSets(e->device, 7, w, 0, NULL);
}
/* The triangle, with what it reads bound: in a subpass or a pass of its own */
@@ -834,28 +859,74 @@ static void draw(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer cmd,
vkCmdDraw(cmd, 3, 1, 0, 0);
}
VkPipeline hdr_vk_subpass_pipeline(hdr_vk_t *e, VkRenderPass render_pass, uint32_t subpass)
static unsigned samples_log2(unsigned samples)
{
return make_pipeline(e, e->frag_subpass, e->pipeline_layout[K_SUBPASS], render_pass, subpass);
unsigned k = 0;
while ((2u << k) <= samples)
k++;
return k;
}
/* A pipeline of SSAA's, its constants @samples (pso_key.h's
* PSO_SC_SSAA_SAMPLES and PSO_SC_SSAA_LOG2N) */
static VkPipeline make_ssaa_pipeline(hdr_vk_t *e, VkShaderModule frag, VkPipelineLayout layout,
VkRenderPass render_pass, uint32_t subpass, unsigned samples)
{
VkSpecializationMapEntry entries[2];
VkSpecializationInfo spec;
int32_t values[2];
values[0] = (int32_t)samples;
values[1] = (int32_t)samples_log2(samples);
entries[0].constantID = 1;
entries[0].offset = 0;
entries[0].size = sizeof(int32_t);
entries[1].constantID = 2;
entries[1].offset = sizeof(int32_t);
entries[1].size = sizeof(int32_t);
spec.mapEntryCount = 2;
spec.pMapEntries = entries;
spec.dataSize = sizeof(values);
spec.pData = values;
return make_pipeline(e, frag, layout, render_pass, subpass, &spec);
}
VkPipeline hdr_vk_subpass_pipeline(hdr_vk_t *e, VkRenderPass render_pass, uint32_t subpass,
unsigned samples)
{
if (samples <= 1)
return make_pipeline(e, e->frag_subpass, e->pipeline_layout[K_SUBPASS], render_pass, subpass, NULL);
return make_ssaa_pipeline(e, e->frag_subpass_ssaa, e->pipeline_layout[K_SUBPASS], render_pass, subpass, samples);
}
bool hdr_vk_sums_ready(hdr_vk_t *e)
{
/* (the sums are of HDR_VK_SUMS_SAMPLES, whatever was drawn) */
if (e->pipeline_sums == VK_NULL_HANDLE)
e->pipeline_sums = make_ssaa_pipeline(e, e->frag_sums, e->pipeline_layout[K_SAMPLED],
e->render_pass, 0, HDR_VK_SUMS_SAMPLES);
return e->pipeline_sums != VK_NULL_HANDLE;
}
void hdr_vk_subpass_draw(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer cmd,
unsigned ring, unsigned slot, VkPipeline pipeline, VkImageView view,
unsigned width, unsigned height, bool unorm8)
VkImageView samples, unsigned width, unsigned height, bool unorm8)
{
hdr_vk_slot_t *s = slot_of(e, ring, slot);
if (!s || pipeline == VK_NULL_HANDLE)
return;
write_set(e, s, s->set_subpass, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, view);
write_set(e, s, s->set_subpass, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, view, samples,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
draw(e, out, cmd, pipeline, e->pipeline_layout[K_SUBPASS], s->set_subpass, width, height, unorm8);
}
bool hdr_vk_encode(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer cmd,
unsigned ring, unsigned slot, uint64_t epoch, unsigned index,
VkImage *image, VkImageView *view, unsigned width, unsigned height, bool unorm8)
VkImage *image, VkImageView *view, unsigned width, unsigned height, bool unorm8,
VkImageView sums)
{
hdr_vk_slot_t *s = slot_of(e, ring, slot);
hdr_vk_image_t *o;
VkPipeline pipeline = e->pipeline;
VkMemoryBarrier barrier;
VkRenderPassBeginInfo rp;
@@ -871,7 +942,17 @@ bool hdr_vk_encode(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer cm
image_destroy(e, o);
return false;
}
write_set(e, s, s->set_sampled, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, *view);
/* (the pipeline for the sums made before the frames, by the renderer:
* hdr_vk_sums_ready(); none here, the picture alone) */
if (sums != VK_NULL_HANDLE && e->pipeline_sums != VK_NULL_HANDLE)
{
pipeline = e->pipeline_sums;
unorm8 = false;
}
else
sums = VK_NULL_HANDLE;
write_set(e, s, s->set_sampled, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, *view, sums,
VK_IMAGE_LAYOUT_GENERAL);
/* what drew the picture, earlier in the frame, before it is read; what
* read the output last, before it is written again */
@@ -892,7 +973,7 @@ bool hdr_vk_encode(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer cm
rp.renderArea.extent.width = width;
rp.renderArea.extent.height = height;
vkCmdBeginRenderPass(cmd, &rp, VK_SUBPASS_CONTENTS_INLINE);
draw(e, out, cmd, e->pipeline, e->pipeline_layout[K_SAMPLED], s->set_sampled, width, height, unorm8);
draw(e, out, cmd, pipeline, e->pipeline_layout[K_SAMPLED], s->set_sampled, width, height, unorm8);
vkCmdEndRenderPass(cmd);
*image = o->image;
+37 -9
View File
@@ -35,7 +35,9 @@ struct hdr_output;
typedef struct hdr_vk hdr_vk_t;
/* The shaders' SPIR-V: SSAA_RESOLVE_VERT's triangle, HDR_ENCODE_FRAG
* (the picture sampled) and HDR_ENCODE_FRAG_SUBPASS (an input attachment) */
* (the picture sampled), HDR_ENCODE_FRAG_SUBPASS (an input attachment),
* HDR_ENCODE_FRAG_SUBPASS_SSAA (and SSAA's samples, a second one) and
* HDR_ENCODE_FRAG_SUMS (the picture sampled, and SSAA's channel sums) */
typedef struct hdr_vk_code
{
const uint32_t *vert;
@@ -44,6 +46,10 @@ typedef struct hdr_vk_code
size_t frag_size;
const uint32_t *frag_subpass;
size_t frag_subpass_size;
const uint32_t *frag_subpass_ssaa;
size_t frag_subpass_ssaa_size;
const uint32_t *frag_sums;
size_t frag_sums_size;
} hdr_vk_code_t;
/* @format: what is handed over, VK_FORMAT_A2B10G10R10_UNORM_PACK32 for
@@ -71,27 +77,49 @@ void hdr_vk_prepare(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer c
/* A pipeline for subpass @subpass of @render_pass (and those compatible
* with it), which reads the picture as its input attachment 0 and writes
* colour attachment 0; the caller's to destroy. VK_NULL_HANDLE when it
* colour attachment 0; with @samples above 1, SSAA's samples the picture
* was resolved from (rend/tile/pvr_resolve.h's rule) are its input
* attachment 1, and where the picture is their resolve their exact
* average is encoded. The caller's to destroy. VK_NULL_HANDLE when it
* could not be made. */
VkPipeline hdr_vk_subpass_pipeline(hdr_vk_t *e, VkRenderPass render_pass, uint32_t subpass);
VkPipeline hdr_vk_subpass_pipeline(hdr_vk_t *e, VkRenderPass render_pass, uint32_t subpass,
unsigned samples);
/* Draws it, in that subpass, over @width x @height: the picture is @view,
* 8 bits a channel when @unorm8. After hdr_vk_prepare() for the same ring
* and slot. */
/* Draws it, in that subpass, over @width x @height: the picture is @view
* (and the samples @samples, for a pipeline made for them), 8 bits a
* channel when @unorm8 (and dithered then). After hdr_vk_prepare() for the
* same ring and slot. */
void hdr_vk_subpass_draw(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer cmd,
unsigned ring, unsigned slot, VkPipeline pipeline, VkImageView view,
unsigned width, unsigned height, bool unorm8);
VkImageView samples, unsigned width, unsigned height, bool unorm8);
/* ---- a pass of its own ---- */
/* What hdr_vk_encode()'s channel sums are of: whatever the samples a
* pixel that were resolved (SSAA's, and the X scaler's pair of pixels'),
* N of them, a sum is (2^HDR_VK_SUMS_LOG2 / N) times the samples' */
#define HDR_VK_SUMS_LOG2 4
#define HDR_VK_SUMS_SAMPLES (1 << HDR_VK_SUMS_LOG2)
/* The pipeline hdr_vk_encode() takes the channel sums with, made now:
* before the frames, by a renderer that will hand them over. False when
* it could not be. */
bool hdr_vk_sums_ready(hdr_vk_t *e);
/* The picture (@image, @view, in the shader-read layout, 8 bits a channel
* when @unorm8), @width x @height, encoded into @cmd, outside a render
* pass, into the image for the frontend's frame @index; @image and @view
* become that image's, in the same layout. Prepares the frame itself.
* False, both as they were, when it could not be. */
* With @sums (an R16G16B16A16_UINT image in the general layout: for each
* pixel the channel sums of the samples the picture was resolved from by
* rend/tile/pvr_resolve.h's rule, scaled to HDR_VK_SUMS_SAMPLES) and
* hdr_vk_sums_ready(), their exact average is encoded where the picture
* is their resolve, as hdr_vk_subpass_pipeline()'s does. False, both as
* they were, when it could not be. */
bool hdr_vk_encode(hdr_vk_t *e, const struct hdr_output *out, VkCommandBuffer cmd,
unsigned ring, unsigned slot, uint64_t epoch, unsigned index,
VkImage *image, VkImageView *view, unsigned width, unsigned height, bool unorm8);
VkImage *image, VkImageView *view, unsigned width, unsigned height, bool unorm8,
VkImageView sums);
#ifdef __cplusplus
}
+43 -1
View File
@@ -60,6 +60,19 @@ public:
plannedSize = 0;
}
/* With SSAA and the HDR output: an image of each pixel's channel sums,
* which the screen's last pass stores (OIT_FINAL_FRAG_S1_SUMS) for the
* encode to take the exact average from (VulkanContext::HdrEncode()).
* Before the renderer is set up, like SetCompact(), or made again for
* a size; made at the next Init(). */
void SetSums(bool on)
{
sums = on;
}
bool Sums() const { return sums; }
// (null without)
vk::ImageView GetSumsView() const { return sumsAttachment ? sumsAttachment->GetImageView() : vk::ImageView(); }
/* The screen's size: the fragment buffer is made for it, at the next
* Init(). Renders to a texture larger than the screen are drawn in
* more bands rather than the buffer made again while a game runs. */
@@ -78,6 +91,8 @@ public:
{ 0, vk::DescriptorType::eStorageBuffer, Bindings, vk::ShaderStageFlagBits::eFragment }, // pixel buffer
{ 1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // pixel counter
{ 2, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment }, // a-buffer pointers
// (SSAA's channel sums, for the HDR output: written only with SetSums(), read by its shader alone)
{ 3, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment },
};
descSetLayout = context->GetDevice().createDescriptorSetLayoutUnique(
@@ -100,7 +115,7 @@ public:
counts = std::unique_ptr<BufferData>(new BufferData(CountSlots * Counts * sizeof(u32), vk::BufferUsageFlagBits::eTransferDst));
std::fill(std::begin(written), std::end(written), 0);
}
if (width > maxWidth || height > maxHeight || !abufferPointerAttachment)
if (width > maxWidth || height > maxHeight || !abufferPointerAttachment || sums != (sumsAttachment != nullptr))
{
maxWidth = std::max(maxWidth, width);
maxHeight = std::max(maxHeight, height);
@@ -120,6 +135,15 @@ public:
abufferPointerAttachment->Init(maxWidth, maxHeight, vk::Format::eR32Uint,
vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eTransferDst);
abufferPointerTransitionNeeded = true;
if (sumsAttachment)
commandPool->DeferDelete(std::move(sumsAttachment));
if (sums)
{
sumsAttachment = std::unique_ptr<FramebufferAttachment>(
new FramebufferAttachment(context->GetPhysicalDevice(), context->GetDevice()));
sumsAttachment->Init(maxWidth, maxHeight, vk::Format::eR16G16B16A16Uint,
vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eSampled, false);
}
firstFrameAfterInit = true;
remake = true;
}
@@ -140,6 +164,9 @@ public:
writeDescriptorSets.push_back(vk::WriteDescriptorSet(*descSet, 1, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr, &pixelCounterBufferInfo, nullptr));
vk::DescriptorImageInfo pointerImageInfo(vk::Sampler(), abufferPointerAttachment->GetImageView(), vk::ImageLayout::eGeneral);
writeDescriptorSets.push_back(vk::WriteDescriptorSet(*descSet, 2, 0, 1, vk::DescriptorType::eStorageImage, &pointerImageInfo, nullptr, nullptr));
vk::DescriptorImageInfo sumsImageInfo(vk::Sampler(), sumsAttachment ? sumsAttachment->GetImageView() : vk::ImageView(), vk::ImageLayout::eGeneral);
if (sumsAttachment)
writeDescriptorSets.push_back(vk::WriteDescriptorSet(*descSet, 3, 0, 1, vk::DescriptorType::eStorageImage, &sumsImageInfo, nullptr, nullptr));
context->GetDevice().updateDescriptorSets(writeDescriptorSets, nullptr);
}
@@ -150,6 +177,18 @@ public:
void OnNewFrame(vk::CommandBuffer commandBuffer)
{
if (sumsAttachment)
{
/* The sums: in the general layout from the first; after that,
* the frame before's encode done reading them before this
* frame's last pass stores them again */
vk::ImageSubresourceRange range(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1);
vk::ImageMemoryBarrier barrier(vk::AccessFlags(), vk::AccessFlagBits::eShaderWrite,
abufferPointerTransitionNeeded ? vk::ImageLayout::eUndefined : vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral,
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, sumsAttachment->GetImage(), range);
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eFragmentShader, {}, nullptr, nullptr,
barrier);
}
if (abufferPointerTransitionNeeded)
{
abufferPointerTransitionNeeded = false;
@@ -262,6 +301,7 @@ public:
pixelCounterReset.reset();
counts.reset();
abufferPointerAttachment.reset();
sumsAttachment.reset();
descSet.reset();
maxWidth = maxHeight = 0;
}
@@ -322,6 +362,8 @@ private:
std::unique_ptr<BufferData> pixelCounter;
std::unique_ptr<BufferData> pixelCounterReset;
std::unique_ptr<FramebufferAttachment> abufferPointerAttachment;
std::unique_ptr<FramebufferAttachment> sumsAttachment;
bool sums = false;
bool abufferPointerTransitionNeeded = false;
bool firstFrameAfterInit = false;
int maxWidth = 0;
+1 -1
View File
@@ -585,7 +585,7 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture)
if (finalPass)
{
cmdBuffer.setViewport(0, vk::Viewport(0.f, 0.f, (float)viewport.extent.width, (float)viewport.extent.height, 0.f, 1.f));
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipelineManager->GetFinalPipeline());
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipelineManager->GetFinalPipeline(false, true));
quadBuffer->Draw(cmdBuffer);
}
}
+12 -7
View File
@@ -55,6 +55,8 @@ public:
}
protected:
// SSAA's channel sums, when the buffers have them (OITBuffers::SetSums())
vk::ImageView GetSumsView() const { return oitBuffers->GetSumsView(); }
void Init(SamplerManager *samplerManager, OITPipelineManager *pipelineManager, OITBuffers *oitBuffers)
{
this->pipelineManager = pipelineManager;
@@ -223,7 +225,9 @@ public:
timer.End(currentCommandBuffer, commandPool->GetIndex());
currentCommandBuffer.end();
currentCommandBuffer = nullptr;
Present();
/* (with SSAA, the samples' channel sums the last pass stored, when
* it did: no write-out pass came after it) */
Present(screenPipelineManager->Sums() && !writeOut.Runs(viewport.extent.height) ? GetSumsView() : vk::ImageView());
}
vk::RenderPass GetRenderPass() const { return screenPipelineManager->GetRenderPass(false, true); }
/* The chip's write-out (../writeout.h), when the frame has one: the
@@ -314,24 +318,25 @@ public:
t.last = scaled ? restoredPicture : finalColorAttachments[(GetCurrentImage() + count - 1) % count]->GetImageView();
return t;
}
// ...and once it is recorded, as EndFrame() ends one of this drawer's
void ComputeEnd()
// ...and once it is recorded, as EndFrame() ends one of this drawer's (@sums: ComputeDrawer::Sums())
void ComputeEnd(vk::ImageView sums)
{
havePicture = true;
restoredPicture = nullptr;
if (carriedPicture)
commandPool->DeferDelete(std::move(carriedPicture));
Present();
Present(sums);
}
protected:
/* The frame submitted and its picture handed over: with the HDR output,
* the picture encoded first, in the same submission (VulkanContext::HdrEncode()) */
void Present()
* the picture encoded first, in the same submission (VulkanContext::HdrEncode()),
* from the channel sums @sums where there are */
void Present(vk::ImageView sums)
{
vk::Image image = finalColorAttachments[GetCurrentImage()]->GetImage();
vk::ImageView view = finalColorAttachments[GetCurrentImage()]->GetImageView();
const bool hdr = GetContext()->HdrEncode(*commandPool, image, view,
vk::Extent2D(viewport.extent.width, viewport.extent.height));
vk::Extent2D(viewport.extent.width, viewport.extent.height), sums);
commandPool->EndFrame();
GetContext()->PresentFrame(image, view, vk::Offset2D(viewport.extent.width, viewport.extent.height), hdr);
}
+6 -4
View File
@@ -250,6 +250,8 @@ void OITPipelineManager::Prepare()
{
if (i == FIXED_FINAL_PER_SAMPLE && samples <= 1)
continue;
if (i == FIXED_FINAL_SUMS && !sums)
continue;
PsoSlot& slot = fixedSlots[i];
slot.key = ((u64)KIND_FIXED << KEY_KIND) | i;
slot.owner = this;
@@ -288,8 +290,8 @@ VkPipeline OITPipelineManager::BuildPipeline(u64 key, vk::PipelineCache cache)
return CreateModVolPipeline((ModVolMode)(i / 3), culls[i % 3], cache);
if (i < FIXED_FINAL)
return CreateTrModVolPipeline((ModVolMode)((i - FIXED_TRMODVOL) / 3), culls[(i - FIXED_TRMODVOL) % 3], cache);
if (i == FIXED_FINAL || i == FIXED_FINAL_PER_SAMPLE)
return CreateFinalPipeline(i == FIXED_FINAL_PER_SAMPLE, cache);
if (i == FIXED_FINAL || i == FIXED_FINAL_PER_SAMPLE || i == FIXED_FINAL_SUMS)
return CreateFinalPipeline(i == FIXED_FINAL_PER_SAMPLE, i == FIXED_FINAL_SUMS, cache);
if (i == FIXED_CLEAR)
return CreateClearPipeline(cache);
return VK_NULL_HANDLE;
@@ -450,7 +452,7 @@ VkPipeline OITPipelineManager::BuildPoly(u64 key, bool general, vk::PipelineCach
return PsoCreate(GetContext()->GetDevice(), cache, graphicsPipelineCreateInfo);
}
VkPipeline OITPipelineManager::CreateFinalPipeline(bool perSample, vk::PipelineCache cache)
VkPipeline OITPipelineManager::CreateFinalPipeline(bool perSample, bool sums, vk::PipelineCache cache)
{
vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = GetQuadInputStateCreateInfo(false);
@@ -509,7 +511,7 @@ VkPipeline OITPipelineManager::CreateFinalPipeline(bool perSample, vk::PipelineC
vk::PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(vk::PipelineDynamicStateCreateFlags(), 2, dynamicStates);
vk::ShaderModule vertex_module = shaderManager->GetFinalVertexShader();
vk::ShaderModule fragment_module = shaderManager->GetFinalShader(perSample);
vk::ShaderModule fragment_module = shaderManager->GetFinalShader(perSample, sums);
const PsoSpecialization specialization(0, renderPasses->GetSamples());
vk::PipelineShaderStageCreateInfo stages[] = {
+15 -5
View File
@@ -280,6 +280,11 @@ public:
Reset();
renderPasses->SetSamples(samples);
}
if ((samples > 1 && oitBuffers->Sums()) != sums)
{
Reset();
sums = samples > 1 && oitBuffers->Sums();
}
renderPasses->MakeAll();
if (!perFrameLayout)
@@ -338,11 +343,15 @@ public:
{
return vk::Pipeline(fixedSlots[FIXED_TRMODVOL + ModVolIndex(mode, cullMode)].pipeline);
}
// @perSample: SSAA's for a render pass that is not the last (OITShaderManager::GetFinalShader())
vk::Pipeline GetFinalPipeline(bool perSample = false) const
/* @perSample: SSAA's for a render pass that is not the last; @sums: the
* last one's, which also stores the channel sums, when there is one
* (OITShaderManager::GetFinalShader(), Sums()) */
vk::Pipeline GetFinalPipeline(bool perSample = false, bool sums = false) const
{
return vk::Pipeline(fixedSlots[perSample ? FIXED_FINAL_PER_SAMPLE : FIXED_FINAL].pipeline);
return vk::Pipeline(fixedSlots[perSample ? FIXED_FINAL_PER_SAMPLE : sums && this->sums ? FIXED_FINAL_SUMS : FIXED_FINAL].pipeline);
}
// the last pass stores SSAA's channel sums (OITBuffers::SetSums(), with samples)
bool Sums() const { return sums; }
int GetFinalSubpass() const { return renderPasses->GetFinalSubpass(); }
u32 GetSamples() const { return renderPasses->GetSamples(); }
vk::Pipeline GetClearPipeline() const
@@ -402,11 +411,11 @@ private:
VkPipeline BuildPoly(u64 key, bool general, vk::PipelineCache cache);
VkPipeline CreateModVolPipeline(ModVolMode mode, int cullMode, vk::PipelineCache cache);
VkPipeline CreateTrModVolPipeline(ModVolMode mode, int cullMode, vk::PipelineCache cache);
VkPipeline CreateFinalPipeline(bool perSample, vk::PipelineCache cache);
VkPipeline CreateFinalPipeline(bool perSample, bool sums, vk::PipelineCache cache);
VkPipeline CreateClearPipeline(vk::PipelineCache cache);
// the fixed pipelines' slots: the modifier volumes' by mode and cull mode, the last subpass's, the lists'
enum { FIXED_MODVOL = 0, FIXED_TRMODVOL = 12, FIXED_FINAL = 24, FIXED_FINAL_PER_SAMPLE, FIXED_CLEAR, FIXED_COUNT };
enum { FIXED_MODVOL = 0, FIXED_TRMODVOL = 12, FIXED_FINAL = 24, FIXED_FINAL_PER_SAMPLE, FIXED_CLEAR, FIXED_FINAL_SUMS, FIXED_COUNT };
// (Xor to Exclusion; cull modes 0 and 1 are both none)
static u32 ModVolIndex(ModVolMode mode, int cullMode) { return (u32)mode * 3 + (cullMode < 2 ? 0 : cullMode - 1); }
@@ -442,6 +451,7 @@ private:
PsoTable pipelines; // the polygons' own
PsoTable general; // the general ones
PsoSlot fixedSlots[FIXED_COUNT] = {};
bool sums = false;
bool prepared = false;
// what the pipelines are made for (one of a kind: all of them work with any)
vk::RenderPass renderPass;
+6 -2
View File
@@ -36,8 +36,11 @@ public:
DEBUG_LOG(RENDERER, "OITVulkanRenderer::Init");
oitBuffers.SetCompact(rend_compute_wanted && ComputeDrawer::DeviceLacks() == nullptr);
oitBuffers.Init(0, 0, &texCommandPool);
samples = SsaaSamples();
/* (with SSAA and the HDR output, the last pass's channel sums, for
* the encode: OITBuffers::SetSums()) */
oitBuffers.SetSums(samples > 1 && GetContext()->HdrSumsReady());
oitBuffers.Init(0, 0, &texCommandPool);
oitShaderManager.SetSamples(samples);
NOTICE_LOG(RENDERER, "Vulkan per-pixel renderer: with SSAA, a translucent fragment takes the samples %s",
GetContext()->postDepthCoverage ? "the depth test passed (VK_EXT_post_depth_coverage)" : "its own depth test passes, at each sample");
@@ -72,6 +75,7 @@ public:
oitBuffers.SetCompact(rend_compute_wanted && ComputeDrawer::DeviceLacks() == nullptr);
const u32 was = samples;
samples = SsaaSamples();
oitBuffers.SetSums(samples > 1 && GetContext()->HdrSumsReady());
if (samples != was)
{
// (a change of SSAA: the shaders, render passes and pipelines of the other count go)
@@ -166,7 +170,7 @@ public:
{
const OITScreenDrawer::ComputeTarget target = screenDrawer.ComputeBegin(last, scaled);
computeDrawer.Record(target.image, target.view, target.last);
screenDrawer.ComputeEnd();
screenDrawer.ComputeEnd(computeDrawer.Sums());
return true;
}
}
+5 -3
View File
@@ -98,10 +98,12 @@ public:
}
/* @perSample: SSAA's, of a render pass that is not the last, which puts
* each sample together; else the last one's, which resolves them too */
vk::ShaderModule GetFinalShader(bool perSample = false) const
* each sample together; else the last one's, which resolves them too,
* and with @sums stores their channel sums (OITBuffers::SetSums()) */
vk::ShaderModule GetFinalShader(bool perSample = false, bool sums = false) const
{
return modules.Get(samples <= 1 ? RSPV_OIT_FINAL_FRAG_S0 : perSample ? RSPV_OIT_FINAL_FRAG_S2 : RSPV_OIT_FINAL_FRAG_S1);
return modules.Get(samples <= 1 ? RSPV_OIT_FINAL_FRAG_S0 : perSample ? RSPV_OIT_FINAL_FRAG_S2
: sums ? RSPV_OIT_FINAL_FRAG_S1_SUMS : RSPV_OIT_FINAL_FRAG_S1);
}
/* SSAA's samples a pixel (1: off): what the pipelines are specialized
* for, and which modules they take */
+7 -4
View File
@@ -678,11 +678,12 @@ vk::UniqueRenderPass MakeSsaaRenderPass(vk::Format colorFormat, u32 samples, vk:
vk::AttachmentReference colorReference(1, vk::ImageLayout::eColorAttachmentOptimal);
vk::AttachmentReference depthReference(2, vk::ImageLayout::eDepthStencilAttachmentOptimal);
vk::AttachmentReference samplesInput(1, vk::ImageLayout::eShaderReadOnlyOptimal);
const vk::AttachmentReference hdrInputs[] = { pictureInput, samplesInput };
vk::SubpassDescription subpasses[] = {
vk::SubpassDescription(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics, 0, nullptr, 1, &colorReference, nullptr, &depthReference),
vk::SubpassDescription(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics, 1, &samplesInput, 1, &pictureReference, nullptr, nullptr),
// the HDR output: the picture encoded where it is
vk::SubpassDescription(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics, 1, &pictureInput, 1, &hdrReference, nullptr, nullptr),
// the HDR output: the picture encoded where it is, from the samples' exact average where it is their resolve
vk::SubpassDescription(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics, 2, hdrInputs, 1, &hdrReference, nullptr, nullptr),
};
const vk::PipelineStageFlags fbStages = vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests
| vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eColorAttachmentOutput;
@@ -702,9 +703,11 @@ vk::UniqueRenderPass MakeSsaaRenderPass(vk::Format colorFormat, u32 samples, vk:
// the picture, to be shown or sampled
vk::SubpassDependency(1, VK_SUBPASS_EXTERNAL, fbStages, fbStages | vk::PipelineStageFlagBits::eTransfer,
vk::AccessFlagBits::eColorAttachmentWrite, fbAll | vk::AccessFlagBits::eTransferRead),
// the HDR output: the picture resolved, then encoded where it is...
// the HDR output: the picture resolved, then encoded where it is (with the samples)...
vk::SubpassDependency(1, 2, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eInputAttachmentRead, vk::DependencyFlagBits::eByRegion),
vk::SubpassDependency(0, 2, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader,
vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eInputAttachmentRead, vk::DependencyFlagBits::eByRegion),
// ...into the image the frontend was done with...
vk::SubpassDependency(VK_SUBPASS_EXTERNAL, 2, vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::AccessFlags(), vk::AccessFlagBits::eColorAttachmentWrite),
@@ -716,7 +719,7 @@ vk::UniqueRenderPass MakeSsaaRenderPass(vk::Format colorFormat, u32 samples, vk:
return context->GetDevice().createRenderPassUnique(vk::RenderPassCreateInfo(vk::RenderPassCreateFlags(),
ARRAY_SIZE(attachmentDescriptions) - (hdr ? 0 : 1), attachmentDescriptions,
ARRAY_SIZE(subpasses) - (hdr ? 0 : 1), subpasses,
ARRAY_SIZE(dependencies) - (hdr ? 0 : 3), dependencies));
ARRAY_SIZE(dependencies) - (hdr ? 0 : 4), dependencies));
}
vk::UniqueRenderPass RttPipelineManager::MakeSsaaRenderPass(u32 samples)
File diff suppressed because it is too large. Load diff
+44 -2
View File
@@ -11,6 +11,25 @@ layout (input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput p
layout (set = 0, binding = 0) uniform sampler2D picture;
#endif
#ifdef SSAA
/* With SSAA (after pso_key.h and pvr_resolve.h), the samples the picture
* was resolved from, read again: where the picture is their resolve
* (pvr_resolve_join(), the rule the resolve wrote it by) their exact
* average is encoded, not the byte it was rounded to; where something was
* drawn over the picture since (the VMUs, the crosshairs), the picture. */
layout (constant_id = PSO_SC_SSAA_SAMPLES) const int SSAA_SAMPLES = 4;
layout (constant_id = PSO_SC_SSAA_LOG2N) const uint SSAA_LOG2N = 2u;
#ifdef SUBPASS
layout (input_attachment_index = 1, set = 0, binding = 13) uniform subpassInputMS samples;
#else
/* (the per-pixel and compute renderers': no samples by the time the
* picture is encoded, but their channel sums, stored by the last pass
* (oit_final.frag) or the walk (pvr_compute.h), scaled to the constants'
* 16 samples - hdr_encode_vk.h's HDR_VK_SUMS_SAMPLES) */
layout (set = 0, binding = 13) uniform usampler2D sums;
#endif
#endif
layout (push_constant) uniform pushBlock
{
vec4 toPanel[3]; /* linear Rec.2020 -> the panel's RGB, columns; zero when unknown */
@@ -22,6 +41,10 @@ layout (push_constant) uniform pushBlock
int sdrEotf;
int scrgb;
int deband; /* the picture is 8 bits a channel */
int ssaaLog2; /* (Direct3D 12's; here a constant: SSAA_LOG2N) */
int pad0;
int pad1;
int pad2;
} pc;
layout (location = 0) out vec4 FragColor;
@@ -29,9 +52,28 @@ layout (location = 0) out vec4 FragColor;
void main()
{
#ifdef SUBPASS
highp vec3 rgb = subpassLoad(picture).rgb;
highp vec4 pic = subpassLoad(picture);
#else
highp vec3 rgb = texelFetch(picture, ivec2(gl_FragCoord.xy), 0).rgb;
highp vec4 pic = texelFetch(picture, ivec2(gl_FragCoord.xy), 0);
#endif
highp vec3 rgb = pic.rgb;
#ifdef SSAA
#ifdef SUBPASS
uint rb = 0u;
uint ga = 0u;
for (int s = 0; s < SSAA_SAMPLES; s++)
{
uint c = packUnorm4x8(subpassLoad(samples, s));
rb += pvr_resolve_rb(c);
ga += pvr_resolve_ga(c);
}
#else
uvec4 t = texelFetch(sums, ivec2(gl_FragCoord.xy), 0);
uint rb = t.r | (t.b << 16);
uint ga = t.g | (t.a << 16);
#endif
if (packUnorm4x8(pic) == pvr_resolve_join(rb, ga, SSAA_LOG2N))
rgb = vec3(float(rb & 0xffffu), float(ga & 0xffffu), float(rb >> 16)) / (255.0 * float(SSAA_SAMPLES));
#endif
if (pc.deband != 0)
rgb = hdr_deband(rgb, gl_FragCoord.xy);
+12 -1
View File
@@ -156,7 +156,12 @@ vec4 blendFragments(int num_frag, vec4 finalColor, uint sampleBit)
#if defined(SSAA) && !defined(SSAA_PER_SAMPLE)
/* SSAA's last subpass: the list sorted once, put together at each sample,
* and the samples resolved by the shared rule (rend/tile/pvr_resolve.h):
* each as the unorm8 the attachment holds, (sum + N/2) / N per channel */
* each as the unorm8 the attachment holds, (sum + N/2) / N per channel.
* With SSAA_SUMS (the HDR output's), the channel sums stored as well, for
* the encode to have the exact average (hdr_encode.frag) */
#ifdef SSAA_SUMS
layout (set = 3, binding = 3, rgba16ui) uniform writeonly restrict uimage2D sums;
#endif
void main(void)
{
ivec2 coords = ivec2(gl_FragCoord.xy);
@@ -170,6 +175,12 @@ void main(void)
ga += pvr_resolve_ga(c);
}
FragColor = unpackUnorm4x8(pvr_resolve_join(rb, ga, SSAA_LOG2N));
#ifdef SSAA_SUMS
/* (scaled to 16 samples: hdr_encode_vk.h's HDR_VK_SUMS_LOG2) */
rb <<= 4u - SSAA_LOG2N;
ga <<= 4u - SSAA_LOG2N;
imageStore(sums, coords, uvec4(rb & 0xffffu, ga & 0xffffu, rb >> 16, ga >> 16));
#endif
}
#elif defined(SSAA)
/* ...and of a render pass that is not the last: each sample put together
+19 -8
View File
@@ -482,7 +482,7 @@ bool VulkanContext::Init(retro_hw_render_interface_vulkan *retro_render_if)
{ vk::DescriptorType::eSampler, 2 },
{ vk::DescriptorType::eCombinedImageSampler, 4000 },
{ vk::DescriptorType::eSampledImage, 2 },
{ vk::DescriptorType::eStorageImage, 12 },
{ vk::DescriptorType::eStorageImage, 24 },
{ vk::DescriptorType::eUniformTexelBuffer, 2 },
{ vk::DescriptorType::eStorageTexelBuffer, 2 },
{ vk::DescriptorType::eUniformBuffer, 36 },
@@ -506,6 +506,8 @@ bool VulkanContext::Init(retro_hw_render_interface_vulkan *retro_render_if)
code.vert = RasterSpvWords(RSPV_SSAA_RESOLVE_VERT, &code.vert_size);
code.frag = RasterSpvWords(RSPV_HDR_ENCODE_FRAG, &code.frag_size);
code.frag_subpass = RasterSpvWords(RSPV_HDR_ENCODE_FRAG_SUBPASS, &code.frag_subpass_size);
code.frag_subpass_ssaa = RasterSpvWords(RSPV_HDR_ENCODE_FRAG_SUBPASS_SSAA, &code.frag_subpass_ssaa_size);
code.frag_sums = RasterSpvWords(RSPV_HDR_ENCODE_FRAG_SUMS, &code.frag_sums_size);
hdr = hdr_vk_new(retro_render_if, heap.Raw(), pipelineCache, hdrFormat, (unsigned)swapChainSize, &code);
if (hdr == nullptr)
ERROR_LOG(RENDERER, "Vulkan: the HDR output could not be set up; the picture goes out as 24-bit");
@@ -671,25 +673,33 @@ void VulkanContext::HdrPrepare(CommandPool& pool, vk::CommandBuffer cmd)
hdr_vk_prepare(hdr, &hdr_output, (VkCommandBuffer)cmd, HdrRing(pool), (unsigned)pool.GetIndex(), pool.GetFrames());
}
vk::UniquePipeline VulkanContext::HdrSubpassPipeline(vk::RenderPass renderPass, u32 subpass)
vk::UniquePipeline VulkanContext::HdrSubpassPipeline(vk::RenderPass renderPass, u32 subpass, u32 samples)
{
if (hdr == nullptr)
return vk::UniquePipeline();
const VkPipeline p = hdr_vk_subpass_pipeline(hdr, (VkRenderPass)renderPass, subpass);
const VkPipeline p = hdr_vk_subpass_pipeline(hdr, (VkRenderPass)renderPass, subpass, samples);
if (p == VK_NULL_HANDLE)
ERROR_LOG(RENDERER, "Vulkan: the HDR output's subpass pipeline could not be made");
return vk::UniquePipeline(vk::Pipeline(p), vk::ObjectDestroy<vk::Device, vk::DispatchLoaderStatic>(device));
}
void VulkanContext::HdrSubpassDraw(CommandPool& pool, vk::CommandBuffer cmd, vk::Pipeline pipeline, vk::ImageView picture, vk::Extent2D extent)
void VulkanContext::HdrSubpassDraw(CommandPool& pool, vk::CommandBuffer cmd, vk::Pipeline pipeline, vk::ImageView picture,
vk::ImageView samples, vk::Extent2D extent)
{
// (every picture is 8 bits a channel)
/* (every picture is 8 bits a channel, dithered; with SSAA's samples,
* what is encoded is their exact average, which is not) */
if (hdr != nullptr)
hdr_vk_subpass_draw(hdr, &hdr_output, (VkCommandBuffer)cmd, HdrRing(pool), (unsigned)pool.GetIndex(),
(VkPipeline)pipeline, (VkImageView)picture, extent.width, extent.height, true);
(VkPipeline)pipeline, (VkImageView)picture, (VkImageView)samples, extent.width, extent.height, !samples);
}
bool VulkanContext::HdrEncode(CommandPool& pool, vk::Image& image, vk::ImageView& view, vk::Extent2D size)
bool VulkanContext::HdrSumsReady()
{
return hdr != nullptr && hdr_vk_sums_ready(hdr);
}
bool VulkanContext::HdrEncode(CommandPool& pool, vk::Image& image, vk::ImageView& view, vk::Extent2D size,
vk::ImageView sums)
{
if (hdr == nullptr)
return false;
@@ -698,7 +708,8 @@ bool VulkanContext::HdrEncode(CommandPool& pool, vk::Image& image, vk::ImageView
vk::CommandBuffer cmd = pool.Allocate();
cmd.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit));
const bool done = hdr_vk_encode(hdr, &hdr_output, (VkCommandBuffer)cmd, HdrRing(pool), (unsigned)pool.GetIndex(),
pool.GetFrames(), (unsigned)GetCurrentImageIndex(), &i, &v, size.width, size.height, true);
pool.GetFrames(), (unsigned)GetCurrentImageIndex(), &i, &v, size.width, size.height, true,
(VkImageView)sums);
cmd.end();
if (done)
{
+14 -4
View File
@@ -63,13 +63,23 @@ public:
void HdrPrepare(CommandPool& pool, vk::CommandBuffer cmd);
/* The encode as subpass @subpass of @renderPass: the picture its input
* attachment 0, the HDR image its colour attachment 0 */
vk::UniquePipeline HdrSubpassPipeline(vk::RenderPass renderPass, u32 subpass);
void HdrSubpassDraw(CommandPool& pool, vk::CommandBuffer cmd, vk::Pipeline pipeline, vk::ImageView picture, vk::Extent2D extent);
// (@samples: SSAA's, above 1 when the subpass also has them as its input attachment 1)
vk::UniquePipeline HdrSubpassPipeline(vk::RenderPass renderPass, u32 subpass, u32 samples = 1);
// (@samples: those the picture was resolved from, with such a pipeline; null without)
void HdrSubpassDraw(CommandPool& pool, vk::CommandBuffer cmd, vk::Pipeline pipeline, vk::ImageView picture,
vk::ImageView samples, vk::Extent2D extent);
/* The picture (@image, @view, shader-readable, @size) encoded in a pass
* of its own, in a command buffer of @pool's, before the pool's
* EndFrame(); @image and @view become the HDR image's. False, both as
* they were, without the HDR output or when it could not be. */
bool HdrEncode(CommandPool& pool, vk::Image& image, vk::ImageView& view, vk::Extent2D size);
* they were, without the HDR output or when it could not be.
* @sums: the channel sums of the samples the picture was resolved
* from (hdr_vk_encode()'s: the per-pixel renderer's, OITBuffers, and
* the compute renderer's), after HdrSumsReady() */
bool HdrEncode(CommandPool& pool, vk::Image& image, vk::ImageView& view, vk::Extent2D size,
vk::ImageView sums = vk::ImageView());
/* The HDR output, and HdrEncode() able to take the channel sums: made
* ready, before the frames */
bool HdrSumsReady();
vk::PhysicalDevice GetPhysicalDevice() const { return physicalDevice; }
vk::Device GetDevice() const { return device; }
+114 -6
View File
@@ -1,6 +1,15 @@
/* ACES 2.0 (Full)'s cube baked off the frame (hdr_transform.c): with
* threads, hdr_dt_fill() hands the bake to a worker and never waits for
* it. This holds the cube it comes back with to the one baked in place
/* ACES 2.0 (Full)'s tables and cube made off the frame (hdr_transform.c):
* with threads, hdr_dt_fill() hands them to a worker and never waits for
* it. The tables:
*
* - the first fill with none draws the tone scale alone, and the fills
* after a change of settings go on with the tables there were, not
* the new ones made there and then (as they were before: a frame of
* several milliseconds);
* - what the fills draw once the worker's are back is what the tables
* made in place (hdr_dt_finish()) give, to the byte.
*
* The cube it comes back with is held to the one baked in place
* (hdr_dt_aces2_cube_finish()), byte for byte:
*
* - baked through, with the frame thread polling as frames do;
@@ -77,13 +86,11 @@ static unsigned fill_until_baked(float paper)
static uint16_t *reference(int gamut, float paper)
{
struct hdr_dt_settings s;
struct hdr_dt_block b;
uint16_t *ref = (uint16_t*)malloc(CUBE_BYTES);
hdr_dt_free();
settings(&s, gamut);
hdr_dt_configure(&s);
hdr_dt_fill(&b, paper, 1000.0f, 1000.0f, xy);
hdr_dt_aces2_cube_finish(paper);
hdr_dt_finish(paper, 1000.0f, 1000.0f, xy, true);
if (!ref || !hdr_dt_aces2_cube())
{
printf("hdr_cube: no reference cube\n");
@@ -94,6 +101,106 @@ static uint16_t *reference(int gamut, float paper)
return ref;
}
/* The block the fills draw for @gamut at @paper with the tables made in
* place, the cube flag aside */
static void reference_block(int gamut, float paper, struct hdr_dt_block *b)
{
struct hdr_dt_settings s;
hdr_dt_free();
settings(&s, gamut);
hdr_dt_configure(&s);
hdr_dt_finish(paper, 1000.0f, 1000.0f, xy, false);
hdr_dt_fill(b, paper, 1000.0f, 1000.0f, xy);
b->gm[2] = 0.0f;
hdr_dt_free();
}
#ifndef TARGET_NO_THREADS
/* Fills until the block is @want, the cube flag aside: the tables the
* worker makes come to it. Each block before is @before's (NULL: one of
* no tables, the tone scale alone), never one of two tables: a fill
* whose cube bake took new tables back half way through gave that,
* reading the ones it had freed. */
static void fill_until(float paper, const struct hdr_dt_block *before, const struct hdr_dt_block *want,
struct hdr_dt_block *b)
{
clock_t start = clock();
for (;;)
{
hdr_dt_fill(b, paper, 1000.0f, 1000.0f, xy);
b->gm[2] = 0.0f;
if (!memcmp(b, want, sizeof(*b)))
return;
if (before ? memcmp(b, before, sizeof(*b)) != 0 : (int)b->mode[0] != HDR_DT_ACES2)
{
printf("hdr_cube: a block neither of the tables before nor of the new ones\n");
failures++;
return;
}
if ((double)(clock() - start) / CLOCKS_PER_SEC > 60.0)
{
printf("hdr_cube: not the tables made in place after a minute\n");
failures++;
return;
}
}
}
#endif
static void same_block(const char *what, const struct hdr_dt_block *b, const struct hdr_dt_block *ref)
{
if (memcmp(b, ref, sizeof(*b)))
{
printf("hdr_cube: %s: the block is not the one the tables made in place give\n", what);
failures++;
}
else
printf("hdr_cube: %s: same\n", what);
}
/* The tables: made off the frame, and the same as made in place */
static void tables(void)
{
struct hdr_dt_settings s;
struct hdr_dt_block b, ref1, ref2, old;
reference_block(1, 200.0f, &ref1);
reference_block(2, 200.0f, &ref2);
settings(&s, 1);
hdr_dt_configure(&s);
hdr_dt_fill(&b, 200.0f, 1000.0f, 1000.0f, xy);
#ifndef TARGET_NO_THREADS
/* none yet: the tone scale alone, nothing solved in the fill */
if ((int)b.mode[0] != HDR_DT_ACES2 || hdr_dt_aces2_lut())
{
printf("hdr_cube: the first fill made the tables itself\n");
failures++;
}
fill_until(200.0f, NULL, &ref1, &b);
#endif
b.gm[2] = 0.0f;
same_block("tables made off the frame", &b, &ref1);
/* another gamut (Rec.2020): the fills go on with these until the new ones are back */
old = b;
settings(&s, 2);
hdr_dt_configure(&s);
hdr_dt_fill(&b, 200.0f, 1000.0f, 1000.0f, xy);
b.gm[2] = 0.0f;
#ifndef TARGET_NO_THREADS
if (memcmp(&b, &old, sizeof(b)))
{
printf("hdr_cube: the fill after a change made the new tables itself\n");
failures++;
}
fill_until(200.0f, &old, &ref2, &b);
#endif
same_block("another gamut's tables", &b, &ref2);
(void)old;
hdr_dt_free();
}
static void same(const char *what, const uint16_t *ref)
{
const uint16_t *cube = hdr_dt_aces2_cube();
@@ -125,6 +232,7 @@ int main(int argc, char **argv)
return 0;
}
tables();
ref200 = reference(1, 200.0f);
ref300 = reference(1, 300.0f);
refp3 = reference(2, 300.0f);
+299 -6
View File
@@ -7,12 +7,23 @@
* it under wine.
*
* hdr_d3d 11|12 out.bin
* hdr_d3d 12ssaa
*
* 12ssaa: the Direct3D 12 encoder with SSAA's samples (hdr_encode.hlsl's
* HDR_ENCODE_SSAA). Known values are drawn into each sample of a 4x
* multisampled picture, the picture is made their resolve by the shared
* rule (rend/tile/pvr_resolve.h), but for a strip drawn over (the VMUs'
* stand-in); the encode of the two is held to that of the samples' exact
* average as a half-float picture - the picture where it was drawn over -
* and has to differ from that of the 8-bit picture: the extra bits are
* there and are the samples'.
*
* C89. */
#define COBJMACROS
#include <windows.h>
#include <d3d11.h>
#include <d3d12.h>
#include <d3dcompiler.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@@ -73,15 +84,14 @@ static void put(void)
cases++;
}
/* (a cube case's tables made for its settings, then its cube baked) */
/* (ACES 2.0 (full)'s tables made now, which frames leave to a worker,
* and a cube case's cube from them) */
static void prepare(const struct hdr_dt_settings *dt, const struct hdr_output *o, int cube)
{
struct hdr_dt_block b;
hdr_dt_configure(dt);
hdr_dt_finish(o->paper_white_nits, o->max_nits, o->panel_peak_nits, o->panel_xy, cube != 0);
if (cube)
{
hdr_dt_fill(&b, o->paper_white_nits, o->max_nits, o->panel_peak_nits, o->panel_xy);
hdr_dt_aces2_cube_finish(o->paper_white_nits);
if (!hdr_dt_aces2_cube())
{
fprintf(stderr, "hdr_d3d: no cube\n");
@@ -318,7 +328,7 @@ static void run12(const char *name, const struct hdr_dt_settings *dt, const stru
void *map;
prepare(dt, o, cube);
if (!hdr_d3d12_encode(enc12[scrgb], list, o, pic12, DXGI_FORMAT_R8G8B8A8_UNORM, W, H, deband != 0,
cases % 3, fence_value + 1, &texture))
NULL, 0, cases % 3, fence_value + 1, &texture))
{
fprintf(stderr, "hdr_d3d: %s: not encoded\n", name);
exit(1);
@@ -333,12 +343,295 @@ static void run12(const char *name, const struct hdr_dt_settings *dt, const stru
put();
}
/* ---- Direct3D 12 with SSAA's samples ---- */
#define SSAA_N 4
#define SSAA_LOG2 2
#define OSD_X 16 /* the columns left of it are "drawn over" */
/* The byte the filler draws at channel @c of sample @s of (x, y): its PS's arithmetic */
static unsigned sample_byte(unsigned x, unsigned y, unsigned s, unsigned c)
{
unsigned v = (x * 73u + y * 151u + s * 61u + c * 97u) * 2654435761u;
/* (a smooth ramp across x, so most samples differ by a few steps, with the hash's low bits) */
return ((x + (v >> 30)) * 255u / (W + 3u)) & 255u;
}
static uint16_t to_half(float f)
{
union { float f; uint32_t u; } v;
uint32_t sign, mant;
int exp;
v.f = f;
sign = (v.u >> 16) & 0x8000u;
exp = (int)((v.u >> 23) & 0xff) - 127 + 15;
mant = v.u & 0x7fffffu;
if (exp <= 0)
return (uint16_t)sign;
if (exp >= 31)
return (uint16_t)(sign | 0x7bffu);
{
uint32_t h = ((uint32_t)exp << 10) | (mant >> 13);
if ((mant & 0x1fffu) > 0x1000u || ((mant & 0x1fffu) == 0x1000u && (h & 1u)))
h++;
return (uint16_t)(sign | h);
}
}
static ID3D12Resource *texture12(DXGI_FORMAT format, unsigned samples, D3D12_RESOURCE_FLAGS flags,
D3D12_RESOURCE_STATES state)
{
D3D12_HEAP_PROPERTIES hp;
D3D12_RESOURCE_DESC rd;
ID3D12Resource *r = NULL;
memset(&hp, 0, sizeof(hp));
hp.Type = D3D12_HEAP_TYPE_DEFAULT;
memset(&rd, 0, sizeof(rd));
rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
rd.Width = W;
rd.Height = H;
rd.DepthOrArraySize = 1;
rd.MipLevels = 1;
rd.Format = format;
rd.SampleDesc.Count = samples;
rd.Flags = flags;
if (FAILED(ID3D12Device_CreateCommittedResource(d12, &hp, D3D12_HEAP_FLAG_NONE, &rd, state, NULL,
&IID_ID3D12Resource, (void**)&r)))
exit(2);
return r;
}
/* @data (W x H of @bpp bytes) into a new texture of @format, in the pixel shader resource state */
static ID3D12Resource *uploaded12(const void *data, DXGI_FORMAT format, unsigned bpp)
{
ID3D12Resource *t = texture12(format, 1, D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
ID3D12Resource *up = buffer12(D3D12_HEAP_TYPE_UPLOAD, (UINT64)W * H * bpp, D3D12_RESOURCE_STATE_GENERIC_READ);
void *map;
ID3D12Resource_Map(up, 0, NULL, &map);
memcpy(map, data, (size_t)W * H * bpp);
ID3D12Resource_Unmap(up, 0, NULL);
copy12(t, up, format, bpp, 1);
barrier12(t, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
submit12();
ID3D12Resource_Release(up);
return t;
}
static ID3DBlob *compile(const char *src, const char *target)
{
pD3DCompile fn = (pD3DCompile)GetProcAddress(LoadLibraryA("d3dcompiler_47.dll"), "D3DCompile");
ID3DBlob *code = NULL, *err = NULL;
if (!fn || FAILED(fn(src, strlen(src), NULL, NULL, NULL, "main", target, 0, 0, &code, &err)))
{
fprintf(stderr, "hdr_d3d: the filler did not compile: %s\n", err ? (const char*)ID3D10Blob_GetBufferPointer(err) : "");
exit(2);
}
return code;
}
/* Each sample of @ms drawn with sample_byte()'s values: a draw for each,
* the pipeline's sample mask picking it and a root constant saying which
* (SV_SampleIndex is not in every compiler wine has) */
static void fill_samples(ID3D12Resource *ms)
{
static const char vs[] =
"float4 main(uint id : SV_VertexID) : SV_Position {"
" float2 p = float2((id << 1) & 2, id & 2); return float4(p * float2(2, -2) + float2(-1, 1), 0, 1); }";
static const char ps[] =
"uint b(uint x, uint y, uint s, uint c) { uint v = (x * 73u + y * 151u + s * 61u + c * 97u) * 2654435761u;"
" return ((x + (v >> 30)) * 255u / (256u + 3u)) & 255u; }"
"cbuffer S : register(b0) { uint s; };"
"float4 main(float4 pos : SV_Position) : SV_Target {"
" uint x = (uint)pos.x, y = (uint)pos.y;"
" return float4(b(x, y, s, 0u), b(x, y, s, 1u), b(x, y, s, 2u), b(x, y, s, 3u)) / 255.0; }";
D3D12_ROOT_SIGNATURE_DESC rsd;
D3D12_ROOT_PARAMETER rp;
ID3DBlob *rsb = NULL, *rse = NULL, *vsb = compile(vs, "vs_5_0"), *psb = compile(ps, "ps_5_0");
ID3D12RootSignature *root = NULL;
ID3D12PipelineState *pso[SSAA_N];
unsigned n;
D3D12_GRAPHICS_PIPELINE_STATE_DESC pd;
D3D12_DESCRIPTOR_HEAP_DESC hd;
ID3D12DescriptorHeap *rtv_heap;
D3D12_CPU_DESCRIPTOR_HANDLE rtv;
D3D12_VIEWPORT vp;
D3D12_RECT sc;
memset(&rp, 0, sizeof(rp));
rp.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
rp.Constants.Num32BitValues = 1;
rp.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
memset(&rsd, 0, sizeof(rsd));
rsd.NumParameters = 1;
rsd.pParameters = &rp;
if (FAILED(D3D12SerializeRootSignature(&rsd, D3D_ROOT_SIGNATURE_VERSION_1, &rsb, &rse))
|| FAILED(ID3D12Device_CreateRootSignature(d12, 0, ID3D10Blob_GetBufferPointer(rsb), ID3D10Blob_GetBufferSize(rsb),
&IID_ID3D12RootSignature, (void**)&root)))
exit(2);
memset(&pd, 0, sizeof(pd));
pd.pRootSignature = root;
pd.VS.pShaderBytecode = ID3D10Blob_GetBufferPointer(vsb);
pd.VS.BytecodeLength = ID3D10Blob_GetBufferSize(vsb);
pd.PS.pShaderBytecode = ID3D10Blob_GetBufferPointer(psb);
pd.PS.BytecodeLength = ID3D10Blob_GetBufferSize(psb);
pd.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
pd.SampleMask = 0xffffffffu;
pd.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
pd.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
pd.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
pd.NumRenderTargets = 1;
pd.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
pd.SampleDesc.Count = SSAA_N;
for (n = 0; n < SSAA_N; n++)
{
pd.SampleMask = 1u << n;
if (FAILED(ID3D12Device_CreateGraphicsPipelineState(d12, &pd, &IID_ID3D12PipelineState, (void**)&pso[n])))
exit(2);
}
memset(&hd, 0, sizeof(hd));
hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
hd.NumDescriptors = 1;
if (FAILED(ID3D12Device_CreateDescriptorHeap(d12, &hd, &IID_ID3D12DescriptorHeap, (void**)&rtv_heap)))
exit(2);
rtv_heap->lpVtbl->GetCPUDescriptorHandleForHeapStart(rtv_heap, &rtv);
ID3D12Device_CreateRenderTargetView(d12, ms, NULL, rtv);
vp.TopLeftX = 0.0f; vp.TopLeftY = 0.0f; vp.Width = (float)W; vp.Height = (float)H; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f;
sc.left = 0; sc.top = 0; sc.right = W; sc.bottom = H;
ID3D12GraphicsCommandList_SetGraphicsRootSignature(list, root);
ID3D12GraphicsCommandList_RSSetViewports(list, 1, &vp);
ID3D12GraphicsCommandList_RSSetScissorRects(list, 1, &sc);
ID3D12GraphicsCommandList_OMSetRenderTargets(list, 1, &rtv, FALSE, NULL);
ID3D12GraphicsCommandList_IASetPrimitiveTopology(list, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
for (n = 0; n < SSAA_N; n++)
{
ID3D12GraphicsCommandList_SetPipelineState(list, pso[n]);
ID3D12GraphicsCommandList_SetGraphicsRoot32BitConstant(list, 0, n, 0);
ID3D12GraphicsCommandList_DrawInstanced(list, 3, 1, 0, 0);
}
barrier12(ms, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
submit12();
}
/* What @picture (of @format, 8 bits a channel when @unorm8; SSAA's @samples or NULL) encodes to, as floats */
static void encode12(hdr_d3d12_t *e, const struct hdr_output *o, ID3D12Resource *picture, DXGI_FORMAT format,
bool unorm8, ID3D12Resource *samples, float *dst)
{
const int scrgb = o->format == HDR_OUTPUT_SCRGB;
const DXGI_FORMAT out = scrgb ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R10G10B10A2_UNORM;
const unsigned bpp = scrgb ? 8 : 4;
ID3D12Resource *texture = NULL;
void *map;
if (!hdr_d3d12_encode(e, list, o, picture, format, W, H, unorm8, samples, samples ? SSAA_LOG2 : 0,
cases % 3, fence_value + 1, &texture))
{
fprintf(stderr, "hdr_d3d: not encoded\n");
exit(1);
}
barrier12(texture, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_SOURCE);
copy12(readback, texture, out, bpp, 0);
barrier12(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
submit12();
ID3D12Resource_Map(readback, 0, NULL, &map);
to_floats((const unsigned char*)map, (size_t)W * bpp, out);
ID3D12Resource_Unmap(readback, 0, NULL);
memcpy(dst, pixels, sizeof(pixels));
cases++;
}
static float ssaa_out[W * H * 4], exact_out[W * H * 4], byte_out[W * H * 4];
static int ssaa12(void)
{
static unsigned char pic8[W * H * 4];
static uint16_t exact16[W * H * 4];
struct hdr_dt_settings dt;
struct hdr_output o;
ID3D12Resource *ms, *pic, *exact;
unsigned x, y, c, i, k;
setup12();
ms = texture12(DXGI_FORMAT_R8G8B8A8_UNORM, SSAA_N, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
D3D12_RESOURCE_STATE_RENDER_TARGET);
fill_samples(ms);
for (y = 0; y < H; y++)
for (x = 0; x < W; x++)
for (c = 0; c < 4; c++)
{
const size_t at = ((size_t)y * W + x) * 4 + c;
unsigned sum = 0, s;
for (s = 0; s < SSAA_N; s++)
sum += sample_byte(x, y, s, c);
if (x < OSD_X)
{
/* drawn over: not the resolve (a byte it cannot be: off by 7) */
pic8[at] = (unsigned char)(((sum + SSAA_N / 2) / SSAA_N + 7u) & 255u);
exact16[at] = to_half((float)pic8[at] / 255.0f);
}
else
{
pic8[at] = (unsigned char)((sum + SSAA_N / 2) / SSAA_N); /* pvr_resolve_join() */
exact16[at] = to_half((float)sum / (255.0f * SSAA_N));
}
}
pic = uploaded12(pic8, DXGI_FORMAT_R8G8B8A8_UNORM, 4);
exact = uploaded12(exact16, DXGI_FORMAT_R16G16B16A16_FLOAT, 8);
memset(&dt, 0, sizeof(dt));
dt.aces2_surround = 1;
dt.gt_toe = 0.22f;
dt.gt_linear = 0.40f;
dt.filmic_contrast = 3;
dt.filmic_max_ev = 4.026068811f;
memset(&o, 0, sizeof(o));
o.paper_white_nits = 200.0f;
o.max_nits = 1000.0f;
for (k = 0; k < 4; k++)
{
unsigned differ = 0, osd_differ = 0;
double worst = 0.0;
dt.curve = (k & 2) ? HDR_DT_AGX : HDR_DT_KNEE;
o.format = (k & 1) ? HDR_OUTPUT_SCRGB : HDR_OUTPUT_HDR10;
hdr_dt_configure(&dt);
hdr_dt_finish(o.paper_white_nits, o.max_nits, o.panel_peak_nits, o.panel_xy, false);
encode12(enc12[k & 1], &o, pic, DXGI_FORMAT_R8G8B8A8_UNORM, true, ms, ssaa_out);
encode12(enc12[k & 1], &o, exact, DXGI_FORMAT_R16G16B16A16_FLOAT, false, NULL, exact_out);
encode12(enc12[k & 1], &o, pic, DXGI_FORMAT_R8G8B8A8_UNORM, false, NULL, byte_out);
for (i = 0; i < W * H * 4; i++)
{
const double a = ssaa_out[i], b = exact_out[i];
/* the half-float picture's rounding and the output's: a 10-bit code (the two
* on either side of a rounding, both quantized), or a half float's */
const double tol = (o.format == HDR_OUTPUT_SCRGB) ? 2e-3 * (fabs(b) > 1.0 ? fabs(b) : 1.0) : 1.001 / 1023.0;
const double d = fabs(a - b) / tol;
if (d > worst)
worst = d;
if (i % 4 != 3 && fabs(ssaa_out[i] - byte_out[i]) > 1e-6)
{
if ((i / 4) % W < OSD_X)
osd_differ++;
else
differ++;
}
}
printf("hdr_d3d: SSAA %s %s: within %.2f of the tolerance of the exact average's; %u channels not the 8-bit picture's, %u drawn over\n",
(k & 2) ? "agx" : "knee", (k & 1) ? "scRGB" : "HDR10", worst, differ, osd_differ);
if (!(worst <= 1.0) || differ < 1000 || osd_differ != 0)
failures++;
}
hdr_d3d12_free(enc12[0]);
hdr_d3d12_free(enc12[1]);
hdr_dt_free();
printf("hdr_d3d: Direct3D 12 with SSAA's samples%s\n", failures ? ": failed" : ": the samples' exact average");
return failures ? 1 : 0;
}
int main(int argc, char **argv)
{
int api;
if (argc == 2 && !strcmp(argv[1], "12ssaa"))
return ssaa12();
if (argc != 3 || (strcmp(argv[1], "11") && strcmp(argv[1], "12")))
{
fprintf(stderr, "usage: hdr_d3d 11|12 out.bin\n");
fprintf(stderr, "usage: hdr_d3d 11|12 out.bin | 12ssaa\n");
return 2;
}
api = atoi(argv[1]);
+4 -1
View File
@@ -3,7 +3,8 @@
# (rend/hdr/hdr_encode_d3d11.c, hdr_encode_d3d12.c) and the DXBC committed
# for them - built with mingw-w64 and run under wine (wined3d and vkd3d,
# on a Vulkan driver: lavapipe will do), over every case hdr_hlsl.sh
# draws, and held to the Vulkan renderer's GLSL (hdr_hlsl --against).
# draws, and held to the Vulkan renderer's GLSL (hdr_hlsl --against);
# and Direct3D 12's with SSAA's samples to their exact average (12ssaa).
#
# sh tools/hdr/hdr_d3d.sh
#
@@ -29,4 +30,6 @@ for api in 11 12; do
wine "$WORK/hdr_d3d.exe" $api "Z:$WORK/d3d$api.bin"
HDR_HLSL_AGAINST="$WORK/d3d$api.bin" sh tools/vulkan/hdr_hlsl.sh
done
# Direct3D 12's encode with SSAA's samples: their exact average
env -u TMPDIR DISPLAY=$DISPLAY_NO WINEDEBUG=-all wine "$WORK/hdr_d3d.exe" 12ssaa
echo "hdr_d3d.sh: passed"
+2 -1
View File
@@ -210,8 +210,9 @@ compute_source_of() {
fi
shift 2
for d in "$@"; do echo "$d"; done
# (and without the blocks for Vulkan's HDR output, PVRC_SUMS: not in these)
for h in core/rend/tile/pvr_lane.h core/rend/tile/pvr_tile.h core/rend/tile/pvr_modvol.h core/rend/tile/pvr_resolve.h core/rend/tile/pvr_scaler.h core/rend/ssaa_pattern.h $PVRC_SRC; do
sed '/^#include/d' $h
sed -e '/^#include/d' -e '/^#if defined(PVRC_SUMS)$/,/^#endif \/\* PVRC_SUMS \*\/$/d' $h
done | sed -e 's/\b\(frm\|rec\|bin\|vtx\|idx\|mvt\|dcmd\|dtrd\|dfrm\)\.w\[/\1_w[/g' \
-e 's/^layout(local_size_x = \(.*\)) in;$/#define PVRC_THREADS \1/' \
-e 's/^\(\(uint\|int\|bool\|float\|uvec4\|vec2\) [A-Za-z_0-9]*\( = [A-Za-z_0-9]*\)\?\(, [A-Za-z_0-9]*\( = [A-Za-z_0-9]*\)\?\)*;\)/static \1/' \
+18 -5
View File
@@ -13,7 +13,10 @@
# the HDR output's encode, one for Direct3D 11
# and 12 both (rend/hdr/hdr_encode.hlsl with
# HDR_ENCODE_VS or HDR_ENCODE_PS;
# hdr/hdr_encode_d3d11.c, hdr_encode_d3d12.c)
# hdr/hdr_encode_d3d11.c, hdr_encode_d3d12.c),
# and Direct3D 12's with SSAA's samples too
# (HDR_ENCODE_SSAA, after rend/tile/pvr_lane.h
# and pvr_resolve.h: PS_SSAA)
#
# sh tools/rend/kernels_dxbc.sh update all made again from their sources
# sh tools/rend/kernels_dxbc.sh update-hdr only the HDR output's made again
@@ -38,7 +41,7 @@ INC_HDR=core/rend/hdr/hdr_encode_dxbc.inc
# kernel: profile
KERNELS="1:cs_5_0 2:cs_5_0 3:vs_5_0 4:ps_5_0"
KERNELS9="1:vs_3_0 2:ps_3_0"
KERNELS_HDR="VS:vs_5_0 PS:ps_5_0"
KERNELS_HDR="VS:vs_5_0 PS:ps_5_0 PS_SSAA:ps_5_0"
CC_WIN=${CC_WIN:-x86_64-w64-mingw32-gcc}
WINE=${WINE-wine}
@@ -50,8 +53,16 @@ source_of() {
echo "#define TEXD_SHADER_HLSL"; echo "#define TEXD_KERNEL_D3D $1"; cat core/rend/texdecode.h
}
# (PS_SSAA: the pixel shader with SSAA's samples, after the shared rules it resolves by)
source_hdr_of() {
echo "#define HDR_ENCODE_$1"; cat core/rend/hdr/hdr_encode.hlsl
case $1 in
PS_SSAA)
echo "#define HDR_ENCODE_PS"; echo "#define HDR_ENCODE_SSAA"
echo "#define PVR_SHADER_HLSL"; echo "#define PVR_LANE_NONE"
cat core/rend/tile/pvr_lane.h core/rend/tile/pvr_resolve.h | grep -v "^#include" ;;
*) echo "#define HDR_ENCODE_$1" ;;
esac
cat core/rend/hdr/hdr_encode.hlsl
}
source9_of() {
@@ -121,8 +132,10 @@ make_inc() {
# (included by C89, so no C++ comments)
make_inc_hdr() {
echo "/* DXBC of the HDR output's encode for Direct3D 11 and 12"
echo " * (rend/hdr/hdr_encode.hlsl with HDR_ENCODE_VS or HDR_ENCODE_PS): made by"
echo " * tools/rend/kernels_dxbc.sh update from it. Not to be edited."
echo " * (rend/hdr/hdr_encode.hlsl with HDR_ENCODE_VS or HDR_ENCODE_PS, and"
echo " * PS_SSAA with HDR_ENCODE_SSAA, after rend/tile/pvr_lane.h and"
echo " * pvr_resolve.h): made by tools/rend/kernels_dxbc.sh update from it."
echo " * Not to be edited."
echo " * d3dcompiler_47 $(compiler_sum)"
sums_hdr
echo " */"
+6
View File
@@ -111,6 +111,8 @@ compute_source() {
echo "#version 450"; echo "#define PVR_SHADER_GLSL"; echo "#define PVRC_SHADER_GLSL"; echo "#define PVRC_KERNEL $1"
if [ "$2" = waterfall ]; then echo "#define PVRC_TEX_WATERFALL"; fi
if [ "$1" = "$PVRC_K_WALK" ]; then echo "#define PVR_LANE_COUNT 8"; echo "#define PVR_LANE_GROUP_SIZE 256"; else echo "#define PVR_LANE_NONE"; fi
# (the HDR output's channel sums, which the walk stores with its constant PVRC_SUMS_ON)
echo "#define PVRC_SUMS"
for h in core/rend/tile/pvr_lane.h core/rend/tile/pvr_tile.h core/rend/tile/pvr_modvol.h core/rend/tile/pvr_resolve.h core/rend/tile/pvr_scaler.h core/rend/ssaa_pattern.h core/rend/tile/pvr_compute.h; do
sed '/^#include/d' $h
done
@@ -314,11 +316,15 @@ raster_list() {
done
echo "OIT_FINAL_FRAG_S0 frag - key oit_header.glsl oit_final.frag"
echo "OIT_FINAL_FRAG_S1 frag SSAA=1 key resolve oit_header.glsl oit_final.frag"
echo "OIT_FINAL_FRAG_S1_SUMS frag SSAA=1,SSAA_SUMS=1 key resolve oit_header.glsl oit_final.frag"
echo "OIT_FINAL_FRAG_S2 frag SSAA=1,SSAA_PER_SAMPLE=1 key resolve oit_header.glsl oit_final.frag"
echo "OIT_FINAL_VERT vert - oit_final.vert"
# (the HDR output's encode, drawn by SSAA_RESOLVE_VERT's triangle: on its own, and as a render pass's last subpass)
echo "HDR_ENCODE_FRAG frag HDR_DT=1 hdr hdr_encode.frag"
echo "HDR_ENCODE_FRAG_SUBPASS frag HDR_DT=1,SUBPASS=1 hdr hdr_encode.frag"
# (...and SSAA's: the samples' exact average where the picture is their resolve)
echo "HDR_ENCODE_FRAG_SUBPASS_SSAA frag HDR_DT=1,SUBPASS=1,SSAA=1 key resolve hdr hdr_encode.frag"
echo "HDR_ENCODE_FRAG_SUMS frag HDR_DT=1,SSAA=1 key resolve hdr hdr_encode.frag"
}
# a raster module's text: @1 its defines, then what it is made of
+827 -3
View File
@@ -2,14 +2,16 @@
"""Write a bootable GD-ROM image (GDI + three track files) around the
bare-metal test program in live_prog.c, for tools/threads/live.sh.
Usage: live_disc.py out.gdi [--no-region-array | --mmu-align]
Usage: live_disc.py out.gdi [--no-region-array | --mmu-align | --slant | --hscale]
live_disc.py out.elf
The program is carried here already compiled, so the test needs no SH-4
compiler; live_prog.c says how PROGRAM was produced. --no-region-array
takes the build of it that renders without ever writing a region array;
--mmu-align the build whose MMU test also asks for address errors on
misaligned accesses.
misaligned accesses; --slant the build with a slanted shaded triangle
over the picture; --hscale that with the screen's renders through the X
scaler as well.
The image is the least the core's HLE BIOS needs to boot it: an IP.BIN
whose bootstrap jumps to the boot file, and an ISO 9660 volume holding
1ST_READ.BIN.
@@ -822,6 +824,824 @@ ffffffff000020440000000017b7d1380000803f00000000ffffffff00000000
0000f04317b7d138000000000000803fffffffff
""")
# The program built with -DLIVE_SLANT (live_prog.c): a shaded triangle
# whose edges slant across the pixels over the middle of the picture, for
# the tests SSAA's resolve matters to (tools/vulkan/vk_hdr.sh --exact).
PROGRAM_SLANT = bytes.fromhex("""
01df02d02b40090000f0008c240e018c020005d119206b200e40543402004227
01170b0009000900fefcffff020005d119205b200e4044340200422601160b00
09000900fefcffff1b4429000b0009000344422642605a200224074402000116
42160b00090002000b0009000e440b0009006a446a000b00090066446a004225
0b0009006a000b000900020101e00b000e4100e00b000900034f01e00b00074f
00e00b0009000900000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000004d105d002600221420002704e402b00090009004440018c
240000ff00000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000016d0026116d229211ad208e522633031128908721045f98b
13d33031068913d3392113d33b210be402a009000dd10ae411d33b2103a00900
215109e45834111004510ed33921184408444b2114100cd22263017332223800
2b000900000000ff00f0ffff00000010000000e000f0ff000000000c0040018c
76010000ff03ffff4040018c220203d12e41fdff2e420b0009000900a800018c
8df09df19df29df39df49df59df69df7830410f020f030f040f050f060f070f0
3df05a000b0009005a440df24df21df25a000b00090048f44cf55af50b000900
48f00b0009000af40b0009000dd10ed2222104710dd2222104710dd222210471
0cd2222101e10cd212220cd200e3322204721222f0720ad33222147212220b00
0900090000e000ac0100008000e1000c0920000100000001e86c5fa0106c5fa0
00e0000c43631843436128411b234b23136418444b2304d210e132221041fc8f
04720b0009000900000000a409d112610f92292108d307d42365336242661367
636159217231028b10431042f68b0b000900ff030c815fa080841e0002d11260
01910b001920ff030c815fa0862f962f224f436804d90b4909008230fb8b264f
f6690b00f66809003c06018cf47ff361319221214d6229444c602d9323673927
7b2011812c60128100e013815d6229455c6029233b2014812c6015810fd101e2
20211871a0e220210dd20ed1206008c8018f1041fa8b0ad1106008c80989f362
09d706e1256331271041fb8f01e00b000c7ffcaf00e0302000ff090084705fa0
18705fa0a086010080705fa027d100e220210c71602163621942047120210871
a0e2202122d223d1206008c8018f1041fa8b1fd1106008c8348d06e11ed34562
21231041fb8b1ad21ad1206008c8018f1041fa8b16d1106008c8258d68261d8d
536400e014d70174180063610a3101416362017202e33232078d017105a001e1
194224041041098d027071622d62036301736233f48f2405f5af104107d11061
0b0001e00b0000e00b0000e084705fa018705fa0a086010080705fa09c705fa0
862f962fa62fb62f224f907f43680ae6f36572d472d00b400c750820018bbba0
09e0fc840fc903886c8f01e0f3600c7002840188668f01e0f3600c7003840288
608f01e0f3620c7225840c63284326840c6118613b2127840c600b21b9931c33
b8922633018b9ea001e08828488d83605cd222621032018b97a002e00ee6f365
59d457d00b400c750820018b8fa009e0f3600c700184118801898aa003e0f360
0c7004840188018985a003e0f3600c7005840288018980a003e0f3620c722b84
0c6328432c840c6118613b212d840c622b217e922c317d922631018b6fa003e0
64e6f36541d43ed00b400c750820688df365f36a107af36800eb6c9414a00ce9
38d2122248a0090010420a8d0177346040c8f98d7b6043610c416f841b20f3af
6f801049088d087ab0280178a36300e78366f076ebaf08e200e12dd64c940ca0
0ae01c311042068dff736821f9891c314a21f7af1d611040068954622c621842
2a217363f1af7362f06001882b8bf36001840288298b1fd22a21f362fa840c62
136319433032228ff3622b840c621c611032ffe00a600840707f264ff66bf66a
f6690b00f668f7af0900f5af0900f3af0900f1af0900efaf0900edaf0900ebaf
0900e9af09e0e7af04e0e5af04e0e3af04e0a8fd2b018000211009008031018c
ec06018c1480018c7431018c6831018c00800000ffff00000ad1422104715221
01e109d21222087212220472122207d1226001c8018d1041fa8b05d1126001c8
0b00290004745fa00c745fa000093d0018745fa0626113671947014725922821
ffe22a62436300432903136001402444136019407a2001c8108943605c302a23
063429073a2773611841236318433c333b21203729022b210b00122643605830
2a2346302907eeaf3a27000249d1126249d11032048d00e248d122210b0000e0
47d148d2122248d221521032718b47d112600188028d11e00b00090044d345d1
3111047144d2292144d645d772166c921b222316726230325d8f00e341d23222
736301e77223226360922033558b39d2215337d22033df8f13e03bd23bd33112
3bd334d733173bd773123bd226164e921b22271639d222623032cd8f16e038d2
2dd3392243932b23371624d305e7722332d332672fd33037318b32d32fd73227
29d7735730372c8b22d3335728d33037b28f18e02cd778162a977b2119162bd1
01e332212ad1126321d11033a48f19e025d338167b22291624d102e2222124d1
126220d110320e8f00e220d122210b0001e00b0011e00b0012e00b0013e00b00
17e00b0018e00b001ae01601c0003601340109000000001044332211100000ff
88776655040000100050018c4040018c5555aaaafc5f018c00f0ff1f0040018c
001000104440018cfc6f018c0df0ad0bb0b0b0b0c0c0c0c00030001008300010
0070018cc1c1c1c100400010000000ff08400010862f962ff45110d2222210d8
422810d0522010d4122410d5622510d6722610d7122710d3f25992238302f359
9222122800e11220122412251226122712238302f6690b00f6680900000000e0
040000e0080000e00c0000e0100000e0140000e0180000e01c0000e0862f962f
a62fb62fc62fd62fe62f224ff07f536b636a7369fd5e2ad11b24431f29dd422d
00e829d1822129d282220871822108728222087182210872822208718221830d
e21f611f722f22dcfc575366a3650b4cb364e21ffc52211f922f2367b366a365
0b4c93641bdee21fa11f922ffc57b366a3650b4cb364f351122d17d112d21222
12d182210872822208718221087282220871822108728222830de21ffc51111f
922f1367b366a3650b4c9364107f264ff66ef66df66cf66bf66af6690b00f668
00000081000000e0040000e0080000e0b40b018c0000803e00000024224ff87f
f351111f722f6367536643650cd10b4100e40cd200e112220bd3122304731223
04731223047312230473122304731223047312238302087f264f0b0009000900
1c0c018c000000e0040000e0862f962fa62fb62fc62fd62fe62ff75cf85bf95e
24d14b2124d2122224da25d1122a25d9522925d8622825d000e1122024d51225
24d6122624d312238302436d2b2dd222b22ae229c22812201225722612238302
d222b22afb5bb229c22812201bdbb225722612238302d222fa5dd22ae229c228
b220122572261223830215dddb244222fa54422afb5dd229c228b220b2257226
12238302f66ef66df66cf66bf66af6690b00f66800000080000000e0040000e0
000000c0080000e00c0000e0100000e0140000e0180000e01c0000e00000803f
000000f0862f962fa62fb62fc62fd62fe62f224fbc7fc07f9bd112619bd22665
266622679ad2266322625b216b217b213b212b2144e0160f96d11060fec90021
ffe295d322230473222300e204732123106001cb002191d1126091d3392005cb
022118718fd33221f471222110718ed222216c718dd222219c718dd222210471
8cd2222110718cd222218cd18cd212228cd212228cd18dd222218dd600e38dd8
8dd412e745330a02094223611c3113652c355c353369583953609c3008408e01
122601731047ed8f047684d184d2222184d185d22221047184d22221047184d2
222184d1c59222210471c392222104711fe222210471be9222217fd27fd310e1
32221041fc8f04727dd1b59222213c717cd222210871077222217bd27bd3ac91
32221041fc8f047279d17ad77ad310e27221a3961c3632261042f98f047177d1
77d2222104719a92222176d276d310e132221041fc8f04721fe174d2122274d2
122274d100e002ea00e973d807e41fe58697869317e6d62f019d230df66df420
1043058d017726657035f989f8af63616ad227536ad22033648d48e001e1160f
10e168d212220472122267d267d30ce132221041fc8f047200e163d212220472
122263d163d2222163d164d2222164d12e4164d1ffe700e308e2722131111042
fb8f08715fd360d1122360d260d1292148977b2111135fd1122200e111125ed2
12225ed212225ed105e222215dd00b40090040e7fc3700175bd1eee233932021
5ad71c3720275ad71c37202759d71c3720271043f38f017157d458d00b4005e5
082001ee028de61fb4a0090054d00b40090054d454d10b41092454dcc36dc51f
bda200ee52d2205335d22033978996af01e1007ce003ff7f8001800000101f7c
e00700f836010048846c5fa0f46c5fa0c0785fa00400d8ff0800d8ff44805fa0
000080003f7d1700000020000401150000007f020000df0177df270000001000
20805fa028815fa08c805fa0000000030c0010a5f831018cabaaaaaa88805fa0
17b7d138000010a500000002000080200000003004905fa0001000a403030303
74805fa000ff000000825fa080800000002006a4050505050606060614905fa0
1ffc0000000006a4070707071c985fa01c9c5fa0004006a4ffff0000c040018c
11111111380000ff000018a500000080180018a54c0e00a02c805fa000001800
a800018c0040018c000000100050018c00f0ff1f443322114040018c000000ff
100000ff2c0a018c000023ac0000ffff0000feff0000fdff5eb000006c06018c
6600018cff7fffff6c00018cc031018c0041018cbd9562d462d00b4009000820
028fe61f42af09005fd460d00b4005e50820018ba3a002ee5dd8ab9b59da5dd9
b3650b4a83640820018b9aa002eea1900c389038f58fb36553d454d00b4008e5
0820018b8fa003ee959553d44dd00b4009000820028f10e187a003ee4fd650d2
20677c6764633c633037028d10417ea004eef58f01724bd17b9210677c674ad3
1c3330633c633037028d1042a8a005eef38f017145d16c9210677c6744d31c33
30633c633037028d10429ba006eef38f017135d435d00b4002e50820018b93a0
08ee3cd13cd2222104715592222101e13ad21222087212220472122238d12260
01c8018d1041fa8b36d1126001c8028ffc717ba009ee00e222210471126001c8
018975a00aee30d1379202a009000c8d017110677c672dd31c3330633c633037
f58d10420beeb1aee61f29d110611c61249220315e8f0cee26d110611c612031
5a8f07ee24d110611c612031568b23d110611c612031538b00ee97aee61f95ae
e61f93aee61f91aee61f8faee61f8daee61f00280004004000180010ee000900
0000200c9809018c5eb000006c06018c0000210c0028210c0000220ce831018c
000020ac000021ac0000ffff000022ac0000feff04745fa00000230c0c745fa0
801a060018745fa0000023ac0000fdff001023ac002820ac002821ac004022ac
54aee61f52aee61f50aee61f4eaee61f4caee61f4aaee61f48aee61f46aee61f
44aee61f017993600888038f936800a1017e936807e00d4846911928936001c9
0b2870e0860fd26af553326bf3667076b3651ed70b47a364071ff36778778366
b3651bd00b40a36440e2fc322e51f7501030318f70e02f5127922921fe022031
028de82ed1a001eecc8ff366860f7076a3650ed70b47a364036bf36678768365
0cd00b40a36440e2fc322e51103b0189bda002ee2f51089339212c522031b28d
017902ee0aa0e51f00030103d409018c1000018c2c00018c01eee51f59d10b41
00e859d00b400900036158d956db0b4b136aa230058f0361a030ffe22a381049
f58b63e1163801ee028fef1f07a209004fd850d1122850d10b41090082624fd3
4ad1326001c8018d1041fa8b4bd11261136001c9081f136001c8018910a202e0
43d1126343d11033018b0ba203e040d1126040d11032038dff88018904a201e0
3bd11260ff885e893ad222213cd13dd2222104713cd222210471629222213bd1
3bd22221147101e2222113622fd1226001c8018d1041fa8b2dd112611c611360
0688018909a204e029d22260294019403088028d747202a204e022632dd22033
0189fea105e022d222d3322224d22ad33222047224d33222047228d332220472
27d33222047227d3322227d727d320e2322701731042fb8f04771cd21cd33222
147201e33222236310d2326001c8018d1042fa8b0ed22260ff88048922600c60
07880089181fa0a1090057afe51f55afe51ff5500470051fe3600a882889f8ae
00e902200c06018c3c06018c80841e0000e100ac785634128c05018c186c5fa0
00e000ac0000008000e1000c046c5fa000e0000c56657273220000800c010022
000000020000006414e000ac0000dec0047d73d1103d018912adc51f71d8836c
057cf36a547a70db84600c69ab80f3640b4b5f74036168e0982929022031028d
160f19a103eeab840c61c8909b201030028dc03812a103eee68b64d8836e147e
63dd64dc64db65da0b4d0900051f8669f36678769365f3640b4c747440e3fc33
3e52f361707110320189f9a004eef3617471a490fe0210320189f3a004ee3c52
f5512a219a21b9213f533a22a9222b211821028de038e7a004eed58b50d00b40
f36b036a4fd9936e187e4fdd6c7b4fdc96680b4d83640038028d02e0d6a005ee
0a2868e0860fb365f3640b4c68740038028d40e2cca006eefc322b51b031028d
903ec7a006eee38b41d1181241d1191241d93dd80b48936440dbf3640b4b6074
3fd40b4807eef3640b4b64740b489364f3643cd10b41687440e7fc377a527851
1032478fe51f36d11a17f3656c75f3642ed00b406874f36432d10b41687440e1
fc311a521951103208ee338fe51f2ed00b4009ee01882d8fe51f2cd00b400aee
0188278fe51f2ad483600b400bee20d428d00b40090028d110301b8fe51f27d4
24d00b4009000188148fe51f24d140e0fc30181000e11a10f3656875f36421d1
0b41607440e3fc333a52385110320cee028de51f42ae0900a36435a083608000
e831018cb831018c4800018ca431018c6600018c5000018cf2830070f3830070
8400018c8c31018c7200018c7a00018c0000803f00000040010004008005018c
010024008605018c8a00018c9800018c01000c006805018c000080bf01000080
785634127605018c0b40090044d800e1122844d10b41090082627e9110320dee
088fe51ff36440d00b4068740788138f00eee51ff2ad0900f0ade51feeade51f
ecade51feaade51fe8ade51fe6ade51fe4ade51fe2ade51f0eeedfade51f33d9
926833d10b41090064e1122931d120e22221136230d1226020c8018f1041fa8b
2ed00b40090029d182212ad120e2222103619c7102e2263129011c31d8ad1f1f
03a0081f01a0081f081f25d1126425d125d22363136600e020e7336528354075
5026017600231047f78f01731cd31fd77223dc7300e772231dd7122704772227
047710e662271997722323651075136000eee71f01e808a010e3d6af081fd4af
081fd2af081f1043248924666c6604677c677036f789f6af871f800111540900
4440018c2c05018c4005018cc8805fa00c06018c00695fa080841e003c06018c
4000a0ff9040018c7040018c018200001000a0ff6ed220622822038901922302
09003e166bd2226210711032228df75ee82e018f03eee71f67d268d112220171
111267d100e332213111321133113411351164d732275fd32223136504731223
047304e66223a893322710715bd004e813a004e35cd112615031d98ff75e5bd1
12611821d48ff75e56d1126002c8d38fe82eceaf090010430789f75ee82efa8f
56660036f789f6af871f4dd334533823048ff75e47d3326330322589e82e2a8d
05ee00e247d122212471422148d149d33b2149d3222349d249d7222732632033
1b8d00e244d3222341d3222345d222213263203301ee248fea1f126241d11032
1b8ff3611fa0787137d222621032d58ff75ed7af00e2d4afe71f726337d22033
ffe22a622a1f00e233d3222330d3222334d2222132632033df89fa5eeb610271
1a1ff3617871399221213890118158e012810fe0138133901481329015812ad1
00e220211871a0e2202128d228d1206008c8018f1041fa8b24d1106008c81a8d
09eef362787223d306e1256771231041fb8b21d10fe2222120d1139222210471
f072222100e11ed30d940d950d961dd80c994aa000e2ecafe61f314420010002
000383001f0fff0077772c01a10f09008040018c1000a0ff6840018c0100feca
5040018c1c00a0ff1400a0ff1800a0ff4c40018c000000e0000014a50df0ad0b
000014e52143341284705fa018705fa0a086010080705fa0002870a0402070a0
000081a04b1714060a071360783001400c37f5e00d4797071a0013670837d59e
ec3771230172027361714632ec8f85315123017202736032f78f617100e16cd2
1222047212220472c193322204721fe332220472bc9332220472122204721222
0472122263d1b4922221047160e2222161d100e0ae9500e6ad97ad935fd25630
d62f019d230df66d0814612101707030f58f027100e15ad21222047212220472
9993322204721fe3322204729093322204721222047212220472122204728c93
3222047248e3322288924ed32223047322230473222304732223047322234ad2
12220472122249d17992222148d600e7769048d47598759373620922236106e5
5d412c311c312c3145310a021365283501455c321942094287021a022831629e
ec31112601771043e68f027600e13ad212220472122204724d93322204721fe3
322204724493322204724d933222047212220472122231d13b922221047160e2
2221d87141922221f65007e11d401fcb40e2fc32061200eeeb1fee1fed1fffee
ec1f00eee91f92a700e8188d5a884d8f648823d12a9222210190230083609012
20d10b4102e4ffa109001ed10b4103e4faa109001bd10b4104e4f5a109001ad1
15922221019023008360641230f800011f3c1f0fff07001070170f0f00190242
ff0fcf07011019fc00780342e003e07f040070a0240070a0000082a090e80000
840070a0ac0070a0c00070a0800070a0000083a06b6f4606040170a0240170a0
1c985fa0e405018c10905fa002890c911038138b06d107d2222183606488178b
05d10392222113a00900c8001f7c0900000070a001c200001c9c5fa0e7911038
0389019023008360b01173d173d2222173d174dc122c74d1126340e2fc323312
72dee2644412c265551201e2222120e1122e6fd1122c6fd16fd612266fd71227
6fd170d0122070d1141f131f00e9921f911f6ed2222f6eddba9700e66dd50b4d
00e46ddbb41fb31f921f911f6bd1122f6bd700e667d50b4d00e46ada922a6ad3
92236ad492246ad592256ad692266ad792276ad092206ad19221830affe369d2
322210e1122e68d1122c68d152d4122452d5122552d753d67226b41fb31f921f
911f52dee22f62d700e652d50b4d00e4922a55d0922055d1922155d2922255d3
922355d4922455d5922555d69226830affee54d7e22757d112611821018b5ca1
01e255d1126177e22631028f01717ba1090051d2122251d112600820018bd9a1
090009e11630018b1ca20a8801704bd102212dd14ce0fe044221047150e0fe05
5221147154e0fe066221087144d22221047144d2222144d111600d6043d11161
1f88028f1d6114a2090041d641d240e1236740e361271043fc8f027729901041
f68f0c322691163829023bd1088f222119d13ad222211e911038018b38d11061
18d117d2222134d112611821018902a2ffe718de33dcc41f13d1131f32dbb21f
32ddd11f11daa22f11d961a0ffe7a0000080000103010601000070a001820000
0000200060805fa048805fa04c805fa00000700100007f0068805fa06c805fa0
0000008044815fa0000000430000003f4c0d018c00008020000080429a99193f
ff0000ff000000e0040000e0080000e00c0000e0100000e0140000e0180000e0
1c0000e014805fa00000740100003f0000ff00ff2480018c2080018c1880018c
00007f020000df01140a70a4c80a70a400f80000804070a42880018c0000c700
010071a00000dc4200008c420000004200e6c3940b49e365c41f61d1131fb21f
60d1111fa22f60d760d5b7940b4900e6c41f5fd1131fb21f5ed1111fa22fffe7
5dd6ac940b49e3655cd1141f5cd1131f5cd1121fd11fa22fffe700e65ad50b49
00e45ad1141f5ad1131f5ad1121f5ad1111fa22fffe759d659d50b4908e459d1
59d2222159d25ad332220472e222047200e33222047257d33222047232220472
322204723222830100e048dd52dc13634edb52da52d953d400e253d563a104e1
1638018b43a7836050d151d2222151d1ffe2222150d10b41090050d110600fc9
0688018b34a509004dd1126118211f894cd1126118211b8f0820028d09e198a5
01e240e4fc341e141f1447d1126004e700e600e5f3640b40787478e0fe011821
028d01e241d1222141d222610171122213a583603fd12cae09003fd122213fd2
222c32d132d22221b41fb31f921f911f2bd1122f3ad700e63ad50b4d00e4922a
22d29222047292220472922204729222047292220472922204729222830a25d1
ffe2222187ae09002fd111611d612fd00031f7892ed101e27dae222180008800
0000304300001043ffffff800000908000009043000040430002003000005c42
0000c0420000f0410000002000002f43000096430000fa4200007a4300000e0c
24008020000000e020000080040000e0000000c00000803f000043430c0000e0
0000003f100000e0180000e044815fa00000008014805fa08c05018c8c705fa0
1080018c0880018cbc00008c0080018c0c80018c000020002480018c00007801
ff00ffff00008020280a78a41ff800001c80018c54daa26c54d1cb21122a54d1
10e22221147153d2222153d153d212220472122252d153d2222153de53dbb41f
53d1131f00e9921f911f52d1122f52dd869700e6e3650b4d00e4b41fb31f921f
911f4ed1122f4ed700e6e3650b4d00e44cd192214cd292220472922204729222
04729222047292220472922204729222830146d1ffe22221c22a45d101e22221
e7ad0900fc8b43d111600d60407111611f88078d1d6140d101e222213cd10be2
d7ad22214d922c313cd33821f689f2af090046977c3139dee821068b36d11261
1821058936d6e1ad090035d6dead09003896dbad090033d1141f33d1131f33d1
121f33d1111f33d1122f00e61ed522d10b4100e4edad090029071ceeed473b27
722340e7fc377357722b2bd76227922a22240c77222729dee225087722278303
1041518d017002c840e6fc36d3160089c31601c822d6df8d038822d6ddaf2907
00802004e0070900f4805fa0000001004c805fa000007c0100003f0068805fa0
44815fa0000000800000802000008042000000430000003f4c0d018c9a99193f
ff0000ff000000e0040000e014805fa01880018c200a7ca41c80018cdfff0000
00f800000000484300809f4300007042000052436666663f080000e00000803f
0000574300006b4385daa41f85d1131f85d9921f85d1111f85dee22fffe785d6
85d586d00b4008e4a41f85d1131f921f84d1111fe22fffe783d684d57fd10b41
08e483da83d4422a83dd84d2222d84db84d1122b84dc85d6622c00e984d39223
84d5922584d7922784d09220830ab22a83d1122d83d7722be22c92239225ffe2
087322239220830ab22a122d7ed3322be22cfc7592257dd1f870122008752225
08709220830ab22a79d0022d722be22cf8751225047592250475222504759225
830a74d5522a022d322be22c68d01220047012200470222004709220830a422a
5ed4422d6cd4422b622c61d69226f870922061d492240c769226830ab22a67d6
622d722be22cfc709220fc7492240870222008749224830ab22a622d322be22c
53d69226fc701220fc7422240c769226830ab22a5ad6622d722be22c4cd71227
9220222408709220830a522a622d322be22c122747d3122322249220830a51d2
241f131f621f50d1111fe22fffe737d5559432de0b4e00e64cd1122a37d0022d
32d1122b4ad1122c30d339d232222ed538d4522438d6922638d79227830a00e3
00e2c36b636affe9736421a004e5236740d141dc236641dddb2725dd722d26de
022e27d0c2201ad7722b29dc122c29d76227922a2224830d122d622e9220222b
222c2227222a2224830d10456c8d017302e1336002c8028d3921c0a401c801c8
01897da403882ed01821d08f13672adcd1af13662ad027d12adcccaf13668000
00006b430000f04100005743000020410000003f00c00034002080204c0d018c
00005c4200000c4200c0003600008020000000e008000080040000e0000000c0
080000e0002480200c0000e000c80088100000e0140000e0180000e01c0000e0
0000f0420000e041000050420000004300002043080000f00004802000002443
000040430000604300008042c8000080000200300000803f00005643c80000e0
0000484300006c43d36abdd1122de36dbcd1122e036cbcdee220bcdb00e9922b
087092209227bad29222bade922e830aa22ab9d1122db9d1122cb9d1122b9220
9227b8d2b2d42224922e830aa22ab6d2222db6d2222c122b92209227b4d22224
922e830ab3d2222ab3d2222db3d2222c122b92209227b2d11224922e830a922a
922d922c922b922092279224922e830aacd5add6611fadd1122fadd75366add0
0b4000e4a8d1111fabd1122fabd7acd6acd5a8d20b4200e4abd1122a922d922c
922b02e2a9d3222306e1a9d4122490d52225122e830a9cd6611fa2d7722fa5d7
a5d6a6d5a6d49bd00b400900922a922d922c922b9dd192219dd2922284d39223
922e830a9fd4441f9fd1131f9fd5521f9fd6611f9fd7722f9fd6a0d5a0d4a1d0
0b40ffe797d1141f9fd1131f97d2221f9ed1111f97d3322f9dd698d598d499d0
0b40ffe7922a922d922c922b87d1922187d292226ed39223922e830a95d4441f
8bd5531f94d6621f94d7711f94d0022f94d795d595d48bd10b4100e68dd2241f
93d3331f8cd4421f92d5511f92d6622f92d78dd58dd483d00b4000e6922a922d
922c922b71d1922171d2922258d39223922e830a8ad18bd222218bd1141f8bd1
131f8bd1121f8bd1111f8bd1122f8bd700e64dd573d00b4000e461d1141f88d1
131f7bd2221f87d1111f6ad3322f86d786d582946bd00b4000e6922a922d922c
922b5ad192215ad2922241d39223922e830a7fd4422f7fd77fd680d580d481d0
0b4009007ad180d780d681d581d47dd20b42122f4ad3341f55d4431f64d5521f
5ed6611f5ed7722f5ed75fd55fd455d00b4000e642d1141f5dd2231f221f5dd3
311f5dd4422f5dd757d558d44dd00b4000e6922a922d922c922b3cd192213cd2
922223d39223922e830a6bd1ffe22221349110380189adab090068d268d12260
01c8018d1041fa8b66d167d2222167d167d22221047167d22221047166d22221
047142d22221047164d2222164d365d220e1222301721041fb8f047362d163d2
2221147101e22221136255d1226001c8018d1041fa8b80ab0900800096000900
02000080000000c0000080200c0000e0180000e01c0000e0333321426666e742
1f856b3f1010e0ff9a19164366e64d4310e010ff000000f0cd4c914333330243
e01010ff0000a0420000403f0000c842000070431c0c018c00006f4300007443
0000524300005c4300000020100000e0140000e0000000430000444300008042
0000020000005c42000068430000f041000048430000003f00c4003000009020
080000044c0d018c00008d4300007a4300c6003000002a430000024300000c43
9a99193fff0000800000909400000002000034430000f0429a99993e0000ff80
60815fa0000000800000d84200008c420000be42000020423333333fffff00ff
0000964300008043ffffff80000090806666663f0000d442000082420000c242
00003442fc0c018c00004d4300009143000039430000874314805fa0186c5fa0
80841e0000e100ac7856341200e000ac2200008000e1000c0c01002200000065
14e000ac0001dec0046c5fa000e0000c63d222610171122283603c88028f4288
a9a00900048ffb5ee82e018ba8a009005cd10b4109005cd10b4110e45bd11262
83639c73aa911633028ff951c6a009002831fc5e163e008b1c1ffd5e123e0089
1d1ffe5e1c3eee1f291f00e148e0fe022822038d1821018922a10900028d3fe1
12a1fd521638018bc2a08360017883603c88028f3ce17da809001638018b64a8
46881e88028f2d886aa80900028f0f886ba8090001899fa183603dd10b4101e4
836208422c3208428c32184209428c326591192283603fc9036108410c311841
34d33c3134d321211043fc8f027151aa090032d1222100e131d210220c720ae3
3022047210222fd1a0e220212ed22fd1206008c8018f1041fa8b2bd1106008c8
238d13e26871212100e221210ae3312121212121212124d224d1206008c8018f
1041fa8b20d1106008c811896871116211601162116211611ad220620fc91840
1c610b211ad257af12221ad1faaf090018d1f7af090018d00b4000e404a00b1f
15d00b4001e40b1f06d10b41090006d10b4110e405d1126267af291f9500ff7f
1080018c0c06018c4c06018c0c00d8ffe405018c000000a500a000000880018c
84705fa09c705fa018705fa0a08601000480018cffff0000a007018caa911038
1f8ff55ee82e1c8956d1a493322156d1ec31291f55d2122255d112611821078d
8360226253d11032038f01c8c2a00ae201c8018be1a009004fd150d2122236af
017887911038b78fce71fd53fc5ee8331633268dfe5e4ad3ec337d911633268d
44e0fe000820278df65e3ed175933221291ffe5e42d2ec326e9116325e8d44e0
fe0008205d8ff65ee82e5d8f48e0fe0008205c8fff5ee82e278df85039d1b9af
ec3130d15a93322137d1b3af291f2dd155933221ddaf291fe82e0e8d58e029d1
fe000221291ffd52fc5ee832449116322dd19f8dfe5ecdaf0900291fd6ae01e1
20d13d922221efaffd520820038df75e26d18faf0c31e82e028925d18aafec31
fa5ee82e028923d184afec31fb5ee82e038d40e020d17dafec3101e1fe021732
038f40e01dd175af2c31fe000188028910d16faf09001ad16caf090019d169af
090019d166af090018d163af090018d160af0900fa00e003c8001f7c007cff03
007e090010905fa00001d0ba4840018c1080018c0d600d600000400050805fa0
43c721fe0008d0ba0200d0ba0009d0ba000ad0ba000bd0ba000dd0ba0007d0ba
4d4d0d600300d0ba0400d0ba0500d0ba0600d0ba5bd11261c971263112895ad1
12611821138959d11262a9911032028957d108a0090057d1126118210a8956d1
01a0090055d156d2122221af836055d1f9af090054d1f6af090054d11daf0900
29071ce61821028d6d4783ab090050d050dc51d64fab090003c903880189bfa8
3fe13fe11638028d83605aae836201c8018befa8090049d1019c230c090034ee
47d222621071203102ee068fe71f45d11261503102ee048de71f019223020900
b8e9019123010900f6e93360038829071ce11d473cd038d136dc1cab1366f48f
182139d041ab09004121017002715630058f20c8d62f019d230df66deeebf38f
2364f1af336401a093628362212101700271a630f78f0388f78d46302c8f6630
2c8b312101705888f38f027127d12892222127d227d1292127d33b2113662367
e07726d518e352271043fc8f047724d30ce77223fc7310e7722322d708e37221
01771043fb8f047183061ed700e101e60195230508e3b0dec8af5362c6af7362
280680000c80018c0880018c0480018c030cd0ba0080018c040cd0ba010cd0ba
4840018c020cd0ba9d5a0d60000020000000484300006c430000803f00004000
1000a0ff1400a0ff000070431c815fa0e040018ce0ffff03000000e011111111
3c0000ffe0ffc000400000006400000000000000400100000e00000000000000
100000000a0000000000000001000400000004000300040001000c0001002400
01000400010000000200000000010000000200000303000000017f80ff000000
000000000100000002000000ffffff7f0000008001000080ffffffff78563412
efbeadde000001005345474120534547414b4154414e41200000000000000000
17b7d1380000000000000000ffffffff000020440000000017b7d1380000803f
00000000ffffffff000000000000f04317b7d138000000000000803fffffffff
""")
# The program built with -DLIVE_SLANT -DLIVE_HSCALE (live_prog.c): the
# slanted triangle, and the screen's renders through the X scaler, each
# pixel written the average of a pair drawn (tools/vulkan/vk_hdr.sh --exact).
PROGRAM_HSCALE = bytes.fromhex("""
01df02d02b40090000f0008c240e018c020005d119206b200e40543402004227
01170b0009000900fefcffff020005d119205b200e4044340200422601160b00
09000900fefcffff1b4429000b0009000344422642605a200224074402000116
42160b00090002000b0009000e440b0009006a446a000b00090066446a004225
0b0009006a000b000900020101e00b000e4100e00b000900034f01e00b00074f
00e00b0009000900000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000004d105d002600221420002704e402b00090009004440018c
240000ff00000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000000000000016d0026116d229211ad208e522633031128908721045f98b
13d33031068913d3392113d33b210be402a009000dd10ae411d33b2103a00900
215109e45834111004510ed33921184408444b2114100cd22263017332223800
2b000900000000ff00f0ffff00000010000000e000f0ff000000000c0040018c
76010000ff03ffff4040018c220203d12e41fdff2e420b0009000900a800018c
8df09df19df29df39df49df59df69df7830410f020f030f040f050f060f070f0
3df05a000b0009005a440df24df21df25a000b00090048f44cf55af50b000900
48f00b0009000af40b0009000dd10ed2222104710dd2222104710dd222210471
0cd2222101e10cd212220cd200e3322204721222f0720ad33222147212220b00
0900090000e000ac0100008000e1000c0920000100000001e86c5fa0106c5fa0
00e0000c43631843436128411b234b23136418444b2304d210e132221041fc8f
04720b0009000900000000a409d112610f92292108d307d42365336242661367
636159217231028b10431042f68b0b000900ff030c815fa080841e0002d11260
01910b001920ff030c815fa0862f962f224f436804d90b4909008230fb8b264f
f6690b00f66809003c06018cf47ff361319221214d6229444c602d9323673927
7b2011812c60128100e013815d6229455c6029233b2014812c6015810fd101e2
20211871a0e220210dd20ed1206008c8018f1041fa8b0ad1106008c80989f362
09d706e1256331271041fb8f01e00b000c7ffcaf00e0302000ff090084705fa0
18705fa0a086010080705fa027d100e220210c71602163621942047120210871
a0e2202122d223d1206008c8018f1041fa8b1fd1106008c8348d06e11ed34562
21231041fb8b1ad21ad1206008c8018f1041fa8b16d1106008c8258d68261d8d
536400e014d70174180063610a3101416362017202e33232078d017105a001e1
194224041041098d027071622d62036301736233f48f2405f5af104107d11061
0b0001e00b0000e00b0000e084705fa018705fa0a086010080705fa09c705fa0
862f962fa62fb62f224f907f43680ae6f36572d472d00b400c750820018bbba0
09e0fc840fc903886c8f01e0f3600c7002840188668f01e0f3600c7003840288
608f01e0f3620c7225840c63284326840c6118613b2127840c600b21b9931c33
b8922633018b9ea001e08828488d83605cd222621032018b97a002e00ee6f365
59d457d00b400c750820018b8fa009e0f3600c700184118801898aa003e0f360
0c7004840188018985a003e0f3600c7005840288018980a003e0f3620c722b84
0c6328432c840c6118613b212d840c622b217e922c317d922631018b6fa003e0
64e6f36541d43ed00b400c750820688df365f36a107af36800eb6c9414a00ce9
38d2122248a0090010420a8d0177346040c8f98d7b6043610c416f841b20f3af
6f801049088d087ab0280178a36300e78366f076ebaf08e200e12dd64c940ca0
0ae01c311042068dff736821f9891c314a21f7af1d611040068954622c621842
2a217363f1af7362f06001882b8bf36001840288298b1fd22a21f362fa840c62
136319433032228ff3622b840c621c611032ffe00a600840707f264ff66bf66a
f6690b00f668f7af0900f5af0900f3af0900f1af0900efaf0900edaf0900ebaf
0900e9af09e0e7af04e0e5af04e0e3af04e0a8fd2b01800021100900ac31018c
ec06018c1480018ca031018c9431018c00800000ffff00000ad1422104715221
01e109d21222087212220472122207d1226001c8018d1041fa8b05d1126001c8
0b00290004745fa00c745fa000093d0018745fa0626113671947014725922821
ffe22a62436300432903136001402444136019407a2001c8108943605c302a23
063429073a2773611841236318433c333b21203729022b210b00122643605830
2a2346302907eeaf3a27000249d1126249d11032048d00e248d122210b0000e0
47d148d2122248d221521032718b47d112600188028d11e00b00090044d345d1
3111047144d2292144d645d772166c921b222316726230325d8f00e341d23222
736301e77223226360922033558b39d2215337d22033df8f13e03bd23bd33112
3bd334d733173bd773123bd226164e921b22271639d222623032cd8f16e038d2
2dd3392243932b23371624d305e7722332d332672fd33037318b32d32fd73227
29d7735730372c8b22d3335728d33037b28f18e02cd778162a977b2119162bd1
01e332212ad1126321d11033a48f19e025d338167b22291624d102e2222124d1
126220d110320e8f00e220d122210b0001e00b0011e00b0012e00b0013e00b00
17e00b0018e00b001ae01601c0003601340109000000001044332211100000ff
88776655040000100050018c4040018c5555aaaafc5f018c00f0ff1f0040018c
001000104440018cfc6f018c0df0ad0bb0b0b0b0c0c0c0c00030001008300010
0070018cc1c1c1c100400010000000ff08400010862f962ff45110d2222210d8
422810d0522010d4122410d5622510d6722610d7122710d3f25992238302f359
9222122800e11220122412251226122712238302f6690b00f6680900000000e0
040000e0080000e00c0000e0100000e0140000e0180000e01c0000e0862f962f
a62fb62fc62fd62fe62f224ff07f536b636a7369fd5e2ad11b24431f29dd422d
00e829d1822129d282220871822108728222087182210872822208718221830d
e21f611f722f22dcfc575366a3650b4cb364e21ffc52211f922f2367b366a365
0b4c93641bdee21fa11f922ffc57b366a3650b4cb364f351122d17d112d21222
12d182210872822208718221087282220871822108728222830de21ffc51111f
922f1367b366a3650b4c9364107f264ff66ef66df66cf66bf66af6690b00f668
00000081000000e0040000e0080000e0b40b018c0000803e00000024224ff87f
f351111f722f6367536643650cd10b4100e40cd200e112220bd3122304731223
04731223047312230473122304731223047312238302087f264f0b0009000900
1c0c018c000000e0040000e0862f962fa62fb62fc62fd62fe62ff75cf85bf95e
24d14b2124d2122224da25d1122a25d9522925d8622825d000e1122024d51225
24d6122624d312238302436d2b2dd222b22ae229c22812201225722612238302
d222b22afb5bb229c22812201bdbb225722612238302d222fa5dd22ae229c228
b220122572261223830215dddb244222fa54422afb5dd229c228b220b2257226
12238302f66ef66df66cf66bf66af6690b00f66800000080000000e0040000e0
000000c0080000e00c0000e0100000e0140000e0180000e01c0000e00000803f
000000f0862f962fa62fb62fc62fd62fe62f224fbc7fc07f6bd112616bd22665
266622676ad2266322625b216b217b213b212b2144e0160f66d11060fec90021
ffe265d322230473222300e204732123106001cb002161d1126061d7792005cb
022160d360d6622360d6222660d261d662226c7260d662229c7260d662220472
5fd662225fd662625fd55b2222265fd206e6622204728c966222126079200dcb
02215bd112235bd15bd222215bd15cd212225cd212225cd15cd222215cd600e3
5cd85dd412e745330a02094223611c3113652c355c353369583953609c300840
8e01122601731047ed8f047653d154d2222154d154d22221047154d222210471
53d2222153d15592222104715392222104711fe2222104714e9222214ed24fd3
10e132221041fc8f04724dd1459222213c714cd222210871077222214ad24bd3
3c9132221041fc8f047249d149d74ad310e2722133961c3632261042f98f0471
46d147d2222104712a92222145d246d310e132221041fc8f04721fe143d21222
43d2122243d100e002ea00e942d807e41fe51697169317e6d62f019d230df66d
fe2010437a8d017726657035f989f8af63614001007ce003ff7f800180000010
1f7ce00700f80900846c5fa0f46c5fa0c0785fa00400d8ff0800d8ff44805fa0
000080005c805fa03f7d170050805fa060805fa0000020000401150000007f02
0000df01f4805fa00000010048805fa07f7e17007c805fa077df270000001000
20805fa028815fa08c805fa0000000030c0010a52432018cabaaaaaa88805fa0
17b7d138000010a500000002000080200000003004905fa0001000a403030303
74805fa000ff000000825fa080800000002006a4050505050606060614905fa0
1ffc0000000006a4070707071c985fa01c9c5fa0004006a4ffff000080d22753
80d22033628d48e001e1160f10e17ed21222047212227dd27dd30ce132221041
fc8f047200e179d212220472122279d179d2222179d17ad222217ad12e417ad1
ffe700e308e2722131111042fb8f087175d376d1122376d276d12921c8977b21
111375d1122200e1111274d2122274d2122274d105e2222173d00b40090040e7
fc37001771d1eee2b393202170d71c37202770d71c3720276fd71c3720271043
f38f01716dd46ed00b4005e5082001ee138fe61f6bd00b4009006bd46bd10b41
09246bdcc36dc51f4fa200ee69d220534cd22033998998af01e18b9566d467d0
0b4009000820e58de61f5cd45cd00b4005e50820018b03a102ee61d87b9b5fda
60d9b3650b4a83640820018bfaa002ee71900c389038f58fb36550d450d00b40
08e50820018befa003ee659556d453d00b4009000820028f10e1e7a003ee53d6
53d220677c6764633c633037028d1041dea004eef58f01724ed14b9210677c67
3bd31c3330633c633037028d1042d1a005eef38f017148d13c9210677c6735d3
1c3330633c633037028d1042c4a006eef38f017131d432d00b4002e50820018b
bca008ee3dd13ed2222104712592222101e13cd2122208721222047212223ad1
226001c8018d1041fa8b38d1126001c8028ffc71a4a009ee00e2222104711260
01c801899ea00aee18d1079262a0090036010048002800040040001800100900
c040018c11111111380000ff000018a500000080180018a54c0e00a02c805fa0
00001800a800018c0040018c000000100050018c00f0ff1f443322114040018c
000000ff100000ff2c0a018c000023ac0000ffff0000feff0000fdff5eb00000
6c06018c6600018cff7fffff6c00018cec31018c0041018c0000200c9809018c
0000210c0028210c0000220c1432018c000020ac000021ac000022ac04745fa0
0000230c0c745fa0801a060018745fa00c8d017110677c674ad31c3330633c63
3037f58d10420beef4aee61f46d110611c6183922031278f0cee44d110611c61
2031238f07ee42d110611c6120311f8b40d110611c6120311c8b00eedaaee61f
d8aee61fd6aee61fd4aee61fd2aee61fd0aee61fceaee61fccaee61fcaaee61f
c8aee61fc6aee61fc4aee61fc2aee61fc0aee61fbeaee61f017993600888038f
93680ca1017e936807e00d4847911928936001c90b2870e0860fd26af553326b
f3667076b36524d70b47a364071ff36778778366b36521d00b40a36440e2fc32
2e51f75010303d8f70e02f5128922921fe022031028de82edda001eecc8ff366
860f7076a36514d70b47a364036bf3667876836512d00b40a36440e2fc322e51
103b0189c9a002ee2f51099339212c522031b28d017902ee16a0e51fee000003
010309000000fdff001023ac002820ac002821ac004022acd409018c1000018c
2c00018c01eee51f59d10b4100e859d00b400900036158d956db0b4b136aa230
058f0361a030ffe22a381049f58b63e1163801ee028fef1f07a209004fd850d1
122850d10b41090082624fd34ad1326001c8018d1041fa8b4bd11261136001c9
081f136001c8018910a202e043d1126343d11033018b0ba203e040d1126040d1
1032038dff88018904a201e03bd11260ff885e893ad222213cd13dd222210471
3cd222210471629222213bd13bd22221147101e2222113622fd1226001c8018d
1041fa8b2dd112611c6113600688018909a204e029d22260294019403088028d
747202a204e022632dd220330189fea105e022d222d3322224d22ad332220472
24d33222047228d33222047227d33222047227d3322227d727d320e232270173
1042fb8f04771cd21cd33222147201e33222236310d2326001c8018d1042fa8b
0ed22260ff88048922600c6007880089181fa0a1090057afe51f55afe51ff550
0470051fe3600a882889ecae00e902200c06018c3c06018c80841e0000e100ac
785634128c05018c186c5fa000e000ac0000008000e1000c046c5fa000e0000c
56657273220000800c010022000000020000006414e000ac0000dec0047d73d1
103d018980adc51f71d8836c057cf36a547a70db84600c69ab80f3640b4b5f74
036168e0982929022031028d160f19a103eeab840c61c8909b201030028dc038
12a103eee68b64d8836e147e63dd64dc64db65da0b4d0900051f8669f3667876
9365f3640b4c747440e3fc333e52f361707110320189f9a004eef3617471a490
fe0210320189f3a004ee3c52f5512a219a21b9213f533a22a9222b211821028d
e038e7a004eed58b50d00b40f36b036a4fd9936e187e4fdd6c7b4fdc96680b4d
83640038028d02e0d6a005ee0a2868e0860fb365f3640b4c68740038028d40e2
cca006eefc322b51b031028d903ec7a006eee38b41d1181241d1191241d93dd8
0b48936440dbf3640b4b60743fd40b4807eef3640b4b64740b489364f3643cd1
0b41687440e7fc377a5278511032478fe51f36d11a17f3656c75f3642ed00b40
6874f36432d10b41687440e1fc311a521951103208ee338fe51f2ed00b4009ee
01882d8fe51f2cd00b400aee0188278fe51f2ad483600b400bee20d428d00b40
090028d110301b8fe51f27d424d00b4009000188148fe51f24d140e0fc301810
00e11a10f3656875f36421d10b41607440e3fc333a52385110320cee028de51f
42ae0900a36435a0836080001432018ce431018c4800018cd031018c6600018c
5000018cf2830070f38300708400018cb831018c7200018c7a00018c0000803f
00000040010004008005018c010024008605018c8a00018c9800018c01000c00
6805018c000080bf01000080785634127605018c0b40090044d800e1122844d1
0b41090082627e9110320dee088fe51ff36440d00b4068740788138f00eee51f
f2ad0900f0ade51feeade51fecade51feaade51fe8ade51fe6ade51fe4ade51f
e2ade51f0eeedfade51f33d9926833d10b41090064e1122931d120e222211362
30d1226020c8018f1041fa8b2ed00b40090029d182212ad120e2222103619c71
02e2263129011c31d8ad1f1f03a0081f01a0081f081f25d1126425d125d22363
136600e020e73365283540755026017600231047f78f01731cd31fd77223dc73
00e772231dd7122704772227047710e662271997722323651075136000eee71f
01e808a010e3d6af081fd4af081fd2af081f1043248924666c6604677c677036
f789f6af871f8001115409004440018c2c05018c4005018cc8805fa00c06018c
00695fa080841e003c06018c4000a0ff9040018c7040018c018200001000a0ff
6ed22062282203890192230209003e166bd2226210711032228df75ee82e018f
03eee71f67d268d112220171111267d100e332213111321133113411351164d7
32275fd32223136504731223047304e66223a893322710715bd004e813a004e3
5cd112615031d98ff75e5bd112611821d48ff75e56d1126002c8d38fe82eceaf
090010430789f75ee82efa8f56660036f789f6af871f4dd334533823048ff75e
47d3326330322589e82e2a8d05ee00e247d122212471422148d149d33b2149d3
222349d249d72227326320331b8d00e244d3222341d3222345d2222132632033
01ee248fea1f126241d110321b8ff3611fa0787137d222621032d58ff75ed7af
00e2d4afe71f726337d22033ffe22a622a1f00e233d3222330d3222334d22221
32632033df89fa5eeb6102711a1ff3617871399221213890118158e012810fe0
138133901481329015812ad100e220211871a0e2202128d228d1206008c8018f
1041fa8b24d1106008c81a8d09eef362787223d306e1256771231041fb8b21d1
0fe2222120d1139222210471f072222100e11ed30d940d950d961dd80c994aa0
00e2ecafe61f314420010002000383001f0fff0077772c01a10f09008040018c
1000a0ff6840018c0100feca5040018c1c00a0ff1400a0ff1800a0ff4c40018c
000000e0000014a50df0ad0b000014e52143341284705fa018705fa0a0860100
80705fa0002870a0402070a0000081a04b1714060a071360783001400c37f5e0
0d4797071a0013670837d59eec3771230172027361714632ec8f853151230172
02736032f78f617100e16cd21222047212220472c193322204721fe332220472
bc93322204721222047212220472122263d1b4922221047160e2222161d100e0
ae9500e6ad97ad935fd25630d62f019d230df66d0814612101707030f58f0271
00e15ad212220472122204729993322204721fe3322204729093322204721222
047212220472122204728c933222047248e3322288924ed32223047322230473
222304732223047322234ad212220472122249d17992222148d600e7769048d4
7598759373620922236106e55d412c311c312c3145310a021365283501455c32
1942094287021a022831629eec31112601771043e68f027600e13ad212220472
122204724d93322204721fe3322204724493322204724d933222047212220472
122231d13b922221047160e22221d87141922221f65007e11d401fcb40e2fc32
061200eeeb1fee1fed1fffeeec1f00eee91f92a700e8188d5a884d8f648823d1
2a922221019023008360901220d10b4102e4ffa109001ed10b4103e4faa10900
1bd10b4104e4f5a109001ad115922221019023008360641230f800011f3c1f0f
ff07001070170f0f00190242ff0fcf07011019fc00780342e003e07f040070a0
240070a0000082a090e80000840070a0ac0070a0c00070a0800070a0000083a0
6b6f4606040170a0240170a01c985fa0e405018c10905fa002890c911038138b
06d107d2222183606488178b05d10392222113a00900c8001f7c0900000070a0
01c200001c9c5fa0e79110380389019023008360b01173d173d2222173d174dc
122c74d1126340e2fc32331272dee2644412c265551201e2222120e1122e6fd1
122c6fd16fd612266fd712276fd170d0122070d1141f131f00e9921f911f6ed2
222f6eddba9700e66dd50b4d00e46ddbb41fb31f921f911f6bd1122f6bd700e6
67d50b4d00e46ada922a6ad392236ad492246ad592256ad692266ad792276ad0
92206ad19221830affe369d2322210e1122e68d1122c68d152d4122452d51225
52d753d67226b41fb31f921f911f52dee22f62d700e652d50b4d00e4922a55d0
922055d1922155d2922255d3922355d4922455d5922555d69226830affee54d7
e22757d112611821018b5ca101e255d1126177e22631028f01717ba1090051d2
122251d112600820018bd9a1090009e11630018b1ca20a8801704bd102212dd1
4ce0fe044221047150e0fe055221147154e0fe066221087144d22221047144d2
222144d111600d6043d111611f88028f1d6114a2090041d641d240e1236740e3
61271043fc8f027729901041f68f0c322691163829023bd1088f222119d13ad2
22211e911038018b38d1106118d117d2222134d112611821018902a2ffe718de
33dcc41f13d1131f32dbb21f32ddd11f11daa22f11d961a0ffe7a00000800001
03010601000070a0018200000000200060805fa048805fa04c805fa000007001
00007f0068805fa06c805fa00000008044815fa0000000430000003f4c0d018c
00008020000080429a99193fff0000ff000000e0040000e0080000e00c0000e0
100000e0140000e0180000e01c0000e014805fa00000740100003f0000ff00ff
2480018c2080018c1880018c00007f020000df01140a70a4c80a70a400f80000
804070a42880018c0000c700010071a00000dc4200008c420000004200e6c394
0b49e365c41f61d1131fb21f60d1111fa22f60d760d5b7940b4900e6c41f5fd1
131fb21f5ed1111fa22fffe75dd6ac940b49e3655cd1141f5cd1131f5cd1121f
d11fa22fffe700e65ad50b4900e45ad1141f5ad1131f5ad1121f5ad1111fa22f
ffe759d659d50b4908e459d159d2222159d25ad332220472e222047200e33222
047257d33222047232220472322204723222830100e048dd52dc13634edb52da
52d953d400e253d563a104e11638018b43a7836050d151d2222151d1ffe22221
50d10b41090050d110600fc90688018b34a509004dd1126118211f894cd11261
18211b8f0820028d09e198a501e240e4fc341e141f1447d1126004e700e600e5
f3640b40787478e0fe011821028d01e241d1222141d222610171122213a58360
3fd12cae09003fd122213fd2222c32d132d22221b41fb31f921f911f2bd1122f
3ad700e63ad50b4d00e4922a22d2922204729222047292220472922204729222
0472922204729222830a25d1ffe2222187ae09002fd111611d612fd00031f789
2ed101e27dae2221800088000000304300001043ffffff800000908000009043
000040430002003000005c420000c0420000f0410000002000002f4300009643
0000fa4200007a4300000e0c24008020000000e020000080040000e0000000c0
0000803f000043430c0000e00000003f100000e0180000e044815fa000000080
14805fa08c05018c8c705fa01080018c0880018cbc00008c0080018c0c80018c
000020002480018c00007801ff00ffff00008020280a78a41ff800001c80018c
54daa26c54d1cb21122a54d110e22221147153d2222153d153d2122204721222
52d153d2222153de53dbb41f53d1131f00e9921f911f52d1122f52dd869700e6
e3650b4d00e4b41fb31f921f911f4ed1122f4ed700e6e3650b4d00e44cd19221
4cd29222047292220472922204729222047292220472922204729222830146d1
ffe22221c22a45d101e22221e7ad0900fc8b43d111600d60407111611f88078d
1d6140d101e222213cd10be2d7ad22214d922c313cd33821f689f2af09004697
7c3139dee821068b36d112611821058936d6e1ad090035d6dead09003896dbad
090033d1141f33d1131f33d1121f33d1111f33d1122f00e61ed522d10b4100e4
edad090029071ceeed473b27722340e7fc377357722b2bd76227922a22240c77
222729dee2250877222783031041518d017002c840e6fc36d3160089c31601c8
22d6df8d038822d6ddaf290700802004e0070900f4805fa0000001004c805fa0
00007c0100003f0068805fa044815fa000000080000080200000804200000043
0000003f4c0d018c9a99193fff0000ff000000e0040000e014805fa01880018c
200a7ca41c80018cdfff000000f800000000484300809f430000704200005243
6666663f080000e00000803f0000574300006b4385daa41f85d1131f85d9921f
85d1111f85dee22fffe785d685d586d00b4008e4a41f85d1131f921f84d1111f
e22fffe783d684d57fd10b4108e483da83d4422a83dd84d2222d84db84d1122b
84dc85d6622c00e984d3922384d5922584d7922784d09220830ab22a83d1122d
83d7722be22c92239225ffe2087322239220830ab22a122d7ed3322be22cfc75
92257dd1f87012200875222508709220830ab22a79d0022d722be22cf8751225
047592250475222504759225830a74d5522a022d322be22c68d0122004701220
0470222004709220830a422a5ed4422d6cd4422b622c61d69226f870922061d4
92240c769226830ab22a67d6622d722be22cfc709220fc749224087022200874
9224830ab22a622d322be22c53d69226fc701220fc7422240c769226830ab22a
5ad6622d722be22c4cd712279220222408709220830a522a622d322be22c1227
47d3122322249220830a51d2241f131f621f50d1111fe22fffe737d5559432de
0b4e00e64cd1122a37d0022d32d1122b4ad1122c30d339d232222ed538d45224
38d6922638d79227830a00e300e2c36b636affe9736421a004e5236740d141dc
236641dddb2725dd722d26de022e27d0c2201ad7722b29dc122c29d76227922a
2224830d122d622e9220222b222c2227222a2224830d10456c8d017302e13360
02c8028d3921c0a401c801c801897da403882ed01821d08f13672adcd1af1366
2ad027d12adcccaf1366800000006b430000f04100005743000020410000003f
00c00034002080204c0d018c00005c4200000c4200c0003600008020000000e0
08000080040000e0000000c0080000e0002480200c0000e000c80088100000e0
140000e0180000e01c0000e00000f0420000e041000050420000004300002043
080000f00004802000002443000040430000604300008042c800008000020030
0000803f00005643c80000e00000484300006c43d36abdd1122de36dbcd1122e
036cbcdee220bcdb00e9922b087092209227bad29222bade922e830aa22ab9d1
122db9d1122cb9d1122b92209227b8d2b2d42224922e830aa22ab6d2222db6d2
222c122b92209227b4d22224922e830ab3d2222ab3d2222db3d2222c122b9220
9227b2d11224922e830a922a922d922c922b922092279224922e830aacd5add6
611fadd1122fadd75366add00b4000e4a8d1111fabd1122fabd7acd6acd5a8d2
0b4200e4abd1122a922d922c922b02e2a9d3222306e1a9d4122490d52225122e
830a9cd6611fa2d7722fa5d7a5d6a6d5a6d49bd00b400900922a922d922c922b
9dd192219dd2922284d39223922e830a9fd4441f9fd1131f9fd5521f9fd6611f
9fd7722f9fd6a0d5a0d4a1d00b40ffe797d1141f9fd1131f97d2221f9ed1111f
97d3322f9dd698d598d499d00b40ffe7922a922d922c922b87d1922187d29222
6ed39223922e830a95d4441f8bd5531f94d6621f94d7711f94d0022f94d795d5
95d48bd10b4100e68dd2241f93d3331f8cd4421f92d5511f92d6622f92d78dd5
8dd483d00b4000e6922a922d922c922b71d1922171d2922258d39223922e830a
8ad18bd222218bd1141f8bd1131f8bd1121f8bd1111f8bd1122f8bd700e64dd5
73d00b4000e461d1141f88d1131f7bd2221f87d1111f6ad3322f86d786d58294
6bd00b4000e6922a922d922c922b5ad192215ad2922241d39223922e830a7fd4
422f7fd77fd680d580d481d00b4009007ad180d780d681d581d47dd20b42122f
4ad3341f55d4431f64d5521f5ed6611f5ed7722f5ed75fd55fd455d00b4000e6
42d1141f5dd2231f221f5dd3311f5dd4422f5dd757d558d44dd00b4000e6922a
922d922c922b3cd192213cd2922223d39223922e830a6bd1ffe2222134911038
0189adab090068d268d1226001c8018d1041fa8b66d167d2222167d167d22221
047167d22221047166d22221047142d22221047164d2222164d365d220e12223
01721041fb8f047362d163d22221147101e22221136255d1226001c8018d1041
fa8b80ab090080009600090002000080000000c0000080200c0000e0180000e0
1c0000e0333321426666e7421f856b3f1010e0ff9a19164366e64d4310e010ff
000000f0cd4c914333330243e01010ff0000a0420000403f0000c84200007043
1c0c018c00006f43000074430000524300005c4300000020100000e0140000e0
0000004300004443000080420000020000005c42000068430000f04100004843
0000003f00c4003000009020080000044c0d018c00008d4300007a4300c60030
00002a430000024300000c439a99193fff000080000090940000000200003443
0000f0429a99993e0000ff8060815fa0000000800000d84200008c420000be42
000020423333333fffff00ff0000964300008043ffffff80000090806666663f
0000d442000082420000c24200003442fc0c018c00004d430000914300003943
0000874314805fa0186c5fa080841e0000e100ac7856341200e000ac22000080
00e1000c0c0100220000006514e000ac0001dec0046c5fa000e0000c63d22261
0171122283603c88028f4288a9a00900048ffb5ee82e018ba8a009005cd10b41
09005cd10b4110e45bd1126283639c73aa911633028ff951c6a009002831fc5e
163e008b1c1ffd5e123e00891d1ffe5e1c3eee1f291f00e148e0fe022822038d
1821018922a10900028d3fe112a1fd521638018bc2a08360017883603c88028f
3ce17da809001638018b64a846881e88028f2d886aa80900028f0f886ba80900
01899fa183603dd10b4101e4836208422c3208428c32184209428c3265911922
83603fc9036108410c31184134d33c3134d321211043fc8f027151aa090032d1
222100e131d210220c720ae33022047210222fd1a0e220212ed22fd1206008c8
018f1041fa8b2bd1106008c8238d13e26871212100e221210ae3312121212121
212124d224d1206008c8018f1041fa8b20d1106008c811896871116211601162
116211611ad220620fc918401c610b211ad257af12221ad1faaf090018d1f7af
090018d00b4000e404a00b1f15d00b4001e40b1f06d10b41090006d10b4110e4
05d1126267af291f9500ff7f1080018c0c06018c4c06018c0c00d8ffe405018c
000000a500a000000880018c84705fa09c705fa018705fa0a08601000480018c
ffff0000a007018caa9110381f8ff55ee82e1c8956d1a493322156d1ec31291f
55d2122255d112611821078d8360226253d11032038f01c8c2a00ae201c8018b
e1a009004fd150d2122236af017887911038b78fce71fd53fc5ee8331633268d
fe5e4ad3ec337d911633268d44e0fe000820278df65e3ed175933221291ffe5e
42d2ec326e9116325e8d44e0fe0008205d8ff65ee82e5d8f48e0fe0008205c8f
ff5ee82e278df85039d1b9afec3130d15a93322137d1b3af291f2dd155933221
ddaf291fe82e0e8d58e029d1fe000221291ffd52fc5ee832449116322dd19f8d
fe5ecdaf0900291fd6ae01e120d13d922221efaffd520820038df75e26d18faf
0c31e82e028925d18aafec31fa5ee82e028923d184afec31fb5ee82e038d40e0
20d17dafec3101e1fe021732038f40e01dd175af2c31fe000188028910d16faf
09001ad16caf090019d169af090019d166af090018d163af090018d160af0900
fa00e003c8001f7c007cff03007e090010905fa00001d0ba4840018c1080018c
0d600d600000400050805fa043c721fe0008d0ba0200d0ba0009d0ba000ad0ba
000bd0ba000dd0ba0007d0ba4d4d0d600300d0ba0400d0ba0500d0ba0600d0ba
5bd11261c971263112895ad112611821138959d11262a9911032028957d108a0
090057d1126118210a8956d101a0090055d156d2122221af836055d1f9af0900
54d1f6af090054d11daf090029071ce61821028d6d4783ab090050d050dc51d6
4fab090003c903880189bfa83fe13fe11638028d83605aae836201c8018befa8
090049d1019c230c090034ee47d222621071203102ee068fe71f45d112615031
02ee048de71f019223020900b8e9019123010900f6e93360038829071ce11d47
3cd038d136dc1cab1366f48f182139d041ab09004121017002715630058f20c8
d62f019d230df66deeebf38f2364f1af336401a093628362212101700271a630
f78f0388f78d46302c8f66302c8b312101705888f38f027127d12892222127d2
27d1292127d33b2113662367e07726d518e352271043fc8f047724d30ce77223
fc7310e7722322d708e3722101771043fb8f047183061ed700e101e601952305
08e3a6dec8af5362c6af7362280680000c80018c0880018c0480018c030cd0ba
0080018c040cd0ba010cd0ba4840018c020cd0ba9d5a0d600000200000004843
00006c430000803f000040001000a0ff1400a0ff000070431c815fa0e040018c
e0ffff03000000e0111111113c0000ffe0ffc000400000006400000000000000
400100000e00000000000000100000000a000000000000000100040000000400
0300040001000c00010024000100040001000000020000000001000000020000
0303000000017f80ff000000000000000100000002000000ffffff7f00000080
01000080ffffffff78563412efbeadde000001005345474120534547414b4154
414e4120000000000000000017b7d1380000000000000000ffffffff00002044
0000000017b7d1380000803f00000000ffffffff000000000000f04317b7d138
000000000000803fffffffff
""")
# The same program built with -DLIVE_MMU_ALIGN: its MMU test also asks for
# the address errors a misaligned access raises (live_prog.c). For a core
# that keeps translations in a table, which they all do: it is the build
@@ -1380,7 +2200,7 @@ def main():
if len(sys.argv) == 2 and sys.argv[1].endswith('.elf'):
write_elf(sys.argv[1])
return 0
if len(sys.argv) not in (2, 3) or sys.argv[2:] not in ([], ['--no-region-array'], ['--mmu-align']):
if len(sys.argv) not in (2, 3) or sys.argv[2:] not in ([], ['--no-region-array'], ['--mmu-align'], ['--slant'], ['--hscale']):
sys.stderr.write(__doc__)
return 2
out = sys.argv[1]
@@ -1389,6 +2209,10 @@ def main():
program = PROGRAM_NO_REGION_ARRAY
elif sys.argv[2:] == ['--mmu-align']:
program = PROGRAM_MMU_ALIGN
elif sys.argv[2:] == ['--slant']:
program = PROGRAM_SLANT
elif sys.argv[2:] == ['--hscale']:
program = PROGRAM_HSCALE
d = os.path.dirname(out) or '.'
ip = bytearray(16 * 2048)
+23 -2
View File
@@ -25,8 +25,9 @@
* sh4-linux-gnu-objcopy -O binary live_prog.elf live_prog.bin
*
* and paste the bytes of live_prog.bin into PROGRAM; the same again with
* -DNO_REGION_ARRAY for PROGRAM_NO_REGION_ARRAY, and with -DLIVE_MMU_ALIGN
* for PROGRAM_MMU_ALIGN. */
* -DNO_REGION_ARRAY for PROGRAM_NO_REGION_ARRAY, with -DLIVE_MMU_ALIGN
* for PROGRAM_MMU_ALIGN, with -DLIVE_SLANT for PROGRAM_SLANT, and with
* -DLIVE_SLANT -DLIVE_HSCALE for PROGRAM_HSCALE. */
typedef unsigned int u32;
typedef unsigned short u16;
#define PVR(off) (*(volatile u32 *)(0xA05F8000 + (off)))
@@ -574,6 +575,16 @@ static void ta_scene(void)
* one's bank has it green. */
ta_quad_two(TSP_PLAIN, (6u << 27) | (0x1000 >> 3), TSP_PLAIN, (6u << 27) | (0x30u << 21) | (0x60000 >> 3),
F(0.5f), F(200.0f), F(214.0f), F(240.0f), F(236.0f));
#ifdef LIVE_SLANT
/* With -DLIVE_SLANT, over the middle of it all: a triangle shaded from
* red through green to blue, whose edges slant across the pixels and
* cover parts of them - what SSAA's samples differ over, and what its
* resolve averages (tools/vulkan/vk_hdr.sh's --exact) */
ta_send(TA_POLYGON | 0x2, ISP_GEQUAL, TSP_PLAIN, 0, 0, 0, 0, 0); /* Gouraud */
ta_send(TA_VERTEX, F(40.3f), F(115.7f), F(0.92f), 0, 0, 0xFFE01010, 0);
ta_send(TA_VERTEX, F(150.1f), F(205.9f), F(0.92f), 0, 0, 0xFF10E010, 0);
ta_send(TA_VERTEX | TA_LAST, F(290.6f), F(130.2f), F(0.92f), 0, 0, 0xFF1010E0, 0);
#endif
ta_send(0, 0, 0, 0, 0, 0, 0, 0); /* end of the opaque list */
ta_volume_part(0, x0, y0, x1, y1, F(0.75f));
@@ -1511,6 +1522,16 @@ void cmain(void)
PVR(0xCC) = 0x00150104; /* SPG_VBLANK_INT: in 260, out 21 */
PVR(0x68) = 639 << 16; /* FB_X_CLIP: 0..639 */
PVR(0x6C) = 479 << 16; /* FB_Y_CLIP: 0..479 */
#ifdef LIVE_HSCALE
/* With -DLIVE_HSCALE, the screen's renders through the X scaler too,
* each pixel written the average of a pair drawn, into a framebuffer of
* 32 bits, which the write-out does not pack */
PVR(0xF4) |= 0x10000; /* SCALER_CTL: hscale */
PVR(0x48) = 6; /* FB_W_CTRL: ARGB8888 */
PVR(0x4C) = 640 * 4 / 8; /* FB_W_LINESTRIDE */
PVR(0x44) = (PVR(0x44) & 0x00800000) | 0xD; /* FB_R_CTRL: enable, 0888 */
PVR(0x5C) = (1u << 20) | (479u << 10) | 639u; /* FB_R_SIZE: 640x480 */
#endif
PVR(0x7C) = 0x0027DF77; /* FPU_PARAM_CFG: 6-word region entries */
PVR(0x20) = 0x100000; /* PARAM_BASE */
PVR(0x128) = 0x100000; /* TA_ISP_BASE */
+3 -6
View File
@@ -800,12 +800,9 @@ static void run_case(const char *name, const struct hdr_dt_settings *dt, const s
double worst = 0.0;
unsigned at = 0;
hdr_dt_configure(dt);
if (cube)
{
/* (the tables made for these settings first, the cube from them) */
hdr_dt_fill(b, o->paper_white_nits, o->max_nits, o->panel_peak_nits, o->panel_xy);
hdr_dt_aces2_cube_finish(o->paper_white_nits);
}
/* (ACES 2.0 (full)'s tables made now, which frames leave to a worker,
* and from them the cube) */
hdr_dt_finish(o->paper_white_nits, o->max_nits, o->panel_peak_nits, o->panel_xy, cube != 0);
hdr_dt_fill(b, o->paper_white_nits, o->max_nits, o->panel_peak_nits, o->panel_xy);
make_tables();
if (cube && (tables[3].v == VK_NULL_HANDLE || b->gm[2] == 0.0f))
+47
View File
@@ -529,6 +529,51 @@ static int same_pictures(const picture_t *a, const picture_t *b)
return 0;
}
/* The scRGB picture (@h) of an SSAA frame against its 24-bit one (@s),
* in the 24-bit picture's steps (the reference knee is the identity below
* paper white, and 2.4 the transfer): each channel decoded is within half
* a step of the byte the resolve rounded it to (and a half float's
* rounding) - what was encoded is the samples' exact average, which no
* dither moved - and some are off the steps, which a byte cannot be. */
static int exact_pictures(const picture_t *s, const picture_t *h)
{
const double paper = 200.0, tol = 0.5 + 0.12;
unsigned long off = 0, n = 0;
double worst = 0.0;
uint32_t i;
int c;
if (s->w != h->w || s->h != h->h || h->format != VK_FORMAT_R16G16B16A16_SFLOAT)
{
fprintf(stderr, "vk_hdr: --exact wants a scRGB picture the size of the 24-bit one\n");
return 2;
}
for (i = 0; i < s->w * s->h; i++)
for (c = 0; c < 3; c++)
{
const double got = half_to_double(((const uint16_t*)h->px)[i * 4 + c]) * 80.0 / paper;
const double e = 255.0 * pow(got > 0.0 ? got : 0.0, 1.0 / 2.4);
const double d = fabs(e - (double)s->px[i * 4 + c]);
n++;
if (d > worst)
worst = d;
if (fabs(e - floor(e + 0.5)) > 0.15)
off++;
}
printf("vk_hdr: %lu channels, %lu off the 24-bit steps, at most %.3f of a step from the 24-bit picture\n",
n, off, worst);
if (worst > tol)
{
fprintf(stderr, "vk_hdr: a channel is %.3f of a step from the 24-bit picture: dithered, or not its resolve\n", worst);
return 1;
}
if (off < 100)
{
fprintf(stderr, "vk_hdr: %lu channels off the 24-bit steps: what was encoded is the bytes, not the samples\n", off);
return 1;
}
return 0;
}
static int compare(const char *sdr_path, const char *hdr_path)
{
return with_pictures(sdr_path, hdr_path, compare_pictures);
@@ -578,6 +623,8 @@ int main(int argc, char **argv)
if (argc == 4 && !strcmp(argv[1], "--compare"))
return compare(argv[2], argv[3]);
if (argc == 4 && !strcmp(argv[1], "--exact"))
return with_pictures(argv[2], argv[3], exact_pictures);
if (argc == 4 && !strcmp(argv[1], "--same"))
return same(argv[2], argv[3]);
if (argc < 5)
+33 -1
View File
@@ -13,6 +13,12 @@
# vk_hdr --compare encodes the 24-bit picture on the CPU and holds the
# HDR one to it;
# - with the chip's write-out on, the picture written out, encoded;
# - with it off, what was resolved - SSAA's samples on every renderer,
# the X scaler's pair of pixels on the compute renderer - is encoded
# from their exact average, not the byte the picture rounded it to,
# and is not dithered (vk_hdr --exact; on the disc with a slanted
# shaded triangle, and that with the X scaler, which the old code
# fails);
# - a filmic curve is not (the comparison sees a wrong encode);
# - every Display Transform draws, and ACES 2.0 (Full) long enough for
# its cube to be made and used.
@@ -31,6 +37,11 @@ trap 'rm -rf "$WORK"' EXIT
${CC:-cc} -O1 -g -Wall -I./core/libretro-common/include -I./core/deps/khronos \
-o "$WORK/vk_hdr" tools/vulkan/vk_hdr.c -ldl -lm
python3 tools/threads/live_disc.py "$WORK/test.gdi"
# (each in a directory of its own: the track files are named alike)
mkdir -p "$WORK/slant" "$WORK/hscale"
python3 tools/threads/live_disc.py "$WORK/slant/slant.gdi" --slant
python3 tools/threads/live_disc.py "$WORK/hscale/hscale.gdi" --hscale
DISC=test.gdi
# (from frame 70 the disc's picture has its textured background, mid-tones
# and saturated colours in it; for its first 64 frames it renders through
# the tile accelerator three frames in four, and writes the fourth to the
@@ -40,7 +51,7 @@ FRAMES=70
run() {
# @1 the frontend, @2 the picture, @3... options
fe=$1; out=$2; shift 2
VKH_DIR="$WORK" VKH_FRONTEND=$fe "$WORK/vk_hdr" "$CORE" "$WORK/test.gdi" "$WORK/$out" $FRAMES "$@" \
VKH_DIR="$WORK" VKH_FRONTEND=$fe "$WORK/vk_hdr" "$CORE" "$WORK/$DISC" "$WORK/$out" $FRAMES "$@" \
2>"$WORK/log.txt" || { cat "$WORK/log.txt"; echo "vk_hdr.sh: $out failed"; exit 1; }
}
@@ -95,6 +106,27 @@ if "$WORK/vk_hdr" --compare "$WORK/wo-strip.raw" "$WORK/strip-hdr10.raw" > /dev/
exit 1
fi
# What was resolved, encoded from its exact average: SSAA's samples, on
# each renderer, the slanted triangle's edges between them...
DISC=slant/slant.gdi
for r in "per-strip (fast, least accurate)|rasterizer|x-strip" \
"per-triangle (normal)|rasterizer|x-triangle" \
"per-pixel (accurate)|rasterizer|x-pixel" \
"per-pixel (accurate)|compute|x-compute"; do
sort=${r%%|*}; rest=${r#*|}; method=${rest%%|*}; name=${rest#*|}
set -- "reicast_alpha_sorting=$sort" "reicast_rendering_method=$method" reicast_ssaa=4x
run sdr "$name.raw" "$@"
run scrgb "$name-scrgb.raw" reicast_color_format=64bit_scrgb "$@"
"$WORK/vk_hdr" --exact "$WORK/$name.raw" "$WORK/$name-scrgb.raw"
done
# ...and the compute renderer's X scaler's pairs of pixels, at one sample
DISC=hscale/hscale.gdi
set -- reicast_pvr2_scaler=enabled "reicast_alpha_sorting=per-pixel (accurate)" reicast_rendering_method=compute
run sdr x-hscale.raw "$@"
run scrgb x-hscale-scrgb.raw reicast_color_format=64bit_scrgb "$@"
"$WORK/vk_hdr" --exact "$WORK/x-hscale.raw" "$WORK/x-hscale-scrgb.raw"
DISC=test.gdi
# (the comparison has to see a wrong encode, or it proves nothing)
run hdr10 aces.raw reicast_color_format=30bit_hdr reicast_hdr_display_transform=aces
if "$WORK/vk_hdr" --compare "$WORK/sdr.raw" "$WORK/aces.raw" > /dev/null 2>&1; then
+3
View File
@@ -56,4 +56,7 @@ run sdr triangle-ssaa reicast_ssaa=4x
run sdr write-out reicast_pvr2_scaler=enabled
run hdr10 hdr10 reicast_color_format=30bit_hdr
run scrgb scrgb reicast_color_format=64bit_scrgb reicast_hdr_display_transform=aces2
# (SSAA's channel sums for the encode: the per-pixel and compute renderers')
run scrgb pixel-ssaa-scrgb reicast_color_format=64bit_scrgb "reicast_alpha_sorting=per-pixel (accurate)" reicast_ssaa=4x
run scrgb compute-ssaa-scrgb reicast_color_format=64bit_scrgb "reicast_alpha_sorting=per-pixel (accurate)" reicast_rendering_method=compute reicast_ssaa=4x
echo "vk_sanitize.sh: passed ($SANITIZE)"