/*
rend/upscaler.h
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see .
*/
#include
#include
#include "grow_buf.h"
#include "upscaler.h"
#include "hw/pvr/ta_ctx.h"
static const struct retro_hw_render_upscaler_interface *upscaler;
static struct retro_hw_upscaler_request request;
static bool requested;
static int renderWidth, renderHeight;
static struct retro_hw_upscaler_inputs inputs;
static unsigned jitterIndex;
static bool resetPending = true;
/* A strip of the frame before: what it was sent under, and where its
* vertices were (an index into the kept positions) */
struct KeptStrip
{
u64 signature;
u32 first;
u32 count;
};
/* What is kept from one frame to the next, in memory that is only grown
* (grow_buf.h): nothing is allocated a frame once it is as large as the
* frames need.
*
* keptStrips/keptXy: the frame before's strips and their vertices' places.
* keptTable: those strips by signature - a signature's place has where its
* strips' indices start in keptIndex, in the order they were sent, and how
* many there are; made by counting, not sorting.
* seenTable: this frame's strips counted by signature, as they are matched.
* nextStrips/nextXy: this frame's, which become the kept ones after. */
static grow_buf_t keptStrips, keptXy, nextStrips, nextXy;
static u32 keptCount, nextCount, nextXyCount;
struct SigSlot
{
u64 signature;
u32 start; /* keptTable: the first of its strips in keptIndex */
u32 count; /* keptTable: its strips; seenTable: seen so far */
u32 frame; /* the frame it is for: older ones are empty */
};
static grow_buf_t keptTable, keptIndex, seenTable;
static u32 keptMask, seenMask, tableFrame;
static u32 sig_hash(u64 signature)
{
return (u32)((signature ^ (signature >> 29)) * 0x9E3779B97F4A7C15ull >> 32);
}
/* @signature's place in @table (@mask + 1 places), made for this frame if
* it has none */
static SigSlot *sig_slot(SigSlot *table, u32 mask, u64 signature)
{
u32 i = sig_hash(signature) & mask;
while (table[i].frame == tableFrame && table[i].signature != signature)
i = (i + 1) & mask;
SigSlot *slot = &table[i];
if (slot->frame != tableFrame)
{
slot->frame = tableFrame;
slot->signature = signature;
slot->start = 0;
slot->count = 0;
}
return slot;
}
/* @signature's place in @table, NULL if it has none this frame */
static const SigSlot *sig_find(const SigSlot *table, u32 mask, u64 signature)
{
for (u32 i = sig_hash(signature) & mask; table[i].frame == tableFrame; i = (i + 1) & mask)
if (table[i].signature == signature)
return &table[i];
return NULL;
}
/* A table of at least twice @entries places (a power of two), all empty */
static SigSlot *sig_table(grow_buf_t *buf, u32 *mask, u32 entries)
{
u32 size = 64;
while (size < entries * 2)
size *= 2;
if (buf->size < size * sizeof(SigSlot))
{
grow_buf_reserve(buf, size * sizeof(SigSlot));
memset(buf->data, 0, buf->size);
}
/* (as large as it has ever been: emptier, and empty for this frame
* whatever it held, for its places are this frame's or not) */
*mask = (u32)(buf->size / sizeof(SigSlot)) - 1;
while ((*mask & (*mask + 1)) != 0)
(*mask)--;
return (SigSlot *)buf->data;
}
void upscaler_set_interface(const struct retro_hw_render_upscaler_interface *iface)
{
upscaler = iface;
requested = false;
renderWidth = renderHeight = 0;
upscaler_reset();
}
bool upscaler_poll(bool wanted)
{
int width = 0, height = 0;
if (wanted && upscaler != NULL && upscaler->get_request(upscaler->handle, &request))
{
width = (int)request.render_width;
height = (int)request.render_height;
}
requested = width > 0 && height > 0;
if (!requested)
width = height = 0;
if (width == renderWidth && height == renderHeight)
return false;
renderWidth = width;
renderHeight = height;
return true;
}
bool upscaler_active()
{
return requested;
}
void upscaler_render_size(int *width, int *height)
{
*width = renderWidth;
*height = renderHeight;
}
/* Halton (2, 3), the sequence DLSS's own samples use, over the phase
* the ratio of the sizes wants; at least 8 frames of it */
static float halton(unsigned index, unsigned base)
{
float r = 0.f, f = 1.f;
for (index++; index > 0; index /= base)
{
f /= base;
r += f * (index % base);
}
return r;
}
static unsigned jitter_phase()
{
if (!requested || renderWidth == 0)
return 8;
const float ratio = (float)request.output_width / (float)renderWidth;
const unsigned phase = (unsigned)ceilf(8.f * ratio * ratio);
return phase < 8 ? 8 : phase > 64 ? 64 : phase;
}
void upscaler_jitter(float *x, float *y)
{
if (!requested)
{
*x = *y = 0.f;
return;
}
const unsigned i = jitterIndex % jitter_phase();
*x = halton(i, 2) - 0.5f;
*y = halton(i, 3) - 0.5f;
}
static u64 strip_signature(const PolyParam& pp)
{
u64 h = 0xcbf29ce484222325ull;
const u32 words[] = { pp.isp.full, pp.tsp.full, pp.tcw.full, pp.pcw.full, pp.count,
(u32)pp.texid, (u32)(pp.texid >> 32) };
for (u32 w : words)
{
h ^= w;
h *= 0x100000001b3ull;
}
return h;
}
/* Further than this, in the game's pixels, nothing on screen is the
* same thing a frame later */
static const float FARTHEST = 160.f;
static void match_list(const rend_context& ctx, const List& list, float *prev_xy)
{
const Vertex *verts = ctx.verts.head();
const u32 *idx = ctx.idx.head();
const KeptStrip *kept_strips = (const KeptStrip *)keptStrips.data;
const float *kept_xy = (const float *)keptXy.data;
const u32 *kept_index = (const u32 *)keptIndex.data;
const SigSlot *kept_table = (const SigSlot *)keptTable.data;
SigSlot *seen_table = (SigSlot *)seenTable.data;
KeptStrip *next_strips = (KeptStrip *)nextStrips.data;
float *next_xy = (float *)nextXy.data;
for (int i = 0; i < list.used(); i++)
{
const PolyParam& pp = list.head()[i];
if (pp.count < 3)
continue;
const u64 signature = strip_signature(pp);
const u32 ordinal = sig_slot(seen_table, seenMask, signature)->count++;
const KeptStrip *kept = NULL;
const SigSlot *slot = keptCount != 0 ? sig_find(kept_table, keptMask, signature) : NULL;
if (slot != NULL && ordinal < slot->count)
kept = &kept_strips[kept_index[slot->start + ordinal]];
/* (the vertices as they are now go to the next frame, matched or not) */
KeptStrip& next = next_strips[nextCount++];
next.signature = signature;
next.first = nextXyCount / 2;
next.count = pp.count;
bool still = kept == NULL;
if (kept != NULL)
{
/* one vertex that went too far, and the strip stands still */
for (u32 v = 0; v < pp.count && !still; v++)
{
const Vertex& vertex = verts[idx[pp.first + v]];
const float px = kept_xy[(kept->first + v) * 2], py = kept_xy[(kept->first + v) * 2 + 1];
if (fabsf(px - vertex.x) > FARTHEST || fabsf(py - vertex.y) > FARTHEST)
still = true;
}
}
for (u32 v = 0; v < pp.count; v++)
{
const u32 vi = idx[pp.first + v];
const Vertex& vertex = verts[vi];
next_xy[nextXyCount++] = vertex.x;
next_xy[nextXyCount++] = vertex.y;
if (!still)
{
prev_xy[vi * 2] = kept_xy[(kept->first + v) * 2];
prev_xy[vi * 2 + 1] = kept_xy[(kept->first + v) * 2 + 1];
}
}
}
}
/* The kept strips by signature (keptTable, keptIndex), from keptStrips:
* counted, the starts summed, the indices placed in the order sent */
static void index_kept(void)
{
const KeptStrip *strips = (const KeptStrip *)keptStrips.data;
SigSlot *table = sig_table(&keptTable, &keptMask, keptCount);
u32 *index = (u32 *)grow_buf_reserve(&keptIndex, (size_t)(keptCount ? keptCount : 1) * sizeof(u32));
u32 i, start = 0;
for (i = 0; i < keptCount; i++)
sig_slot(table, keptMask, strips[i].signature)->count++;
for (i = 0; i <= keptMask; i++)
if (table[i].frame == tableFrame)
{
table[i].start = start;
start += table[i].count;
table[i].count = 0;
}
for (i = 0; i < keptCount; i++)
{
SigSlot *slot = sig_slot(table, keptMask, strips[i].signature);
index[slot->start + slot->count++] = i;
}
}
void upscaler_match(const rend_context& ctx, float *prev_xy)
{
const u32 count = (u32)ctx.verts.used();
const Vertex *verts = ctx.verts.head();
for (u32 i = 0; i < count; i++)
{
prev_xy[i * 2] = verts[i].x;
prev_xy[i * 2 + 1] = verts[i].y;
}
if (resetPending)
keptCount = 0;
/* this frame's room: as many strips and vertices as its lists have */
const List *lists[2] = { &ctx.global_param_op, &ctx.global_param_pt };
u32 strips = 0, vertices = 0;
for (int l = 0; l < 2; l++)
{
strips += (u32)lists[l]->used();
for (int i = 0; i < lists[l]->used(); i++)
vertices += lists[l]->head()[i].count;
}
grow_buf_reserve(&nextStrips, (size_t)(strips ? strips : 1) * sizeof(KeptStrip));
grow_buf_reserve(&nextXy, (size_t)(vertices ? vertices : 1) * 2 * sizeof(float));
nextCount = nextXyCount = 0;
/* (this frame's places: the kept table's, the frame before's, are
* looked up with the frame they were made for) */
tableFrame++;
if (tableFrame == 0)
{
/* (after four billion frames: every place emptied) */
if (seenTable.data != NULL)
memset(seenTable.data, 0, seenTable.size);
if (keptTable.data != NULL)
memset(keptTable.data, 0, keptTable.size);
tableFrame = 1;
keptCount = 0;
}
sig_table(&seenTable, &seenMask, strips);
if (keptCount != 0)
{
/* the kept table was made last frame, for last frame: made again
* for this one (its places are found by the frame they are for) */
index_kept();
}
match_list(ctx, ctx.global_param_op, prev_xy);
match_list(ctx, ctx.global_param_pt, prev_xy);
grow_buf_swap(&keptStrips, &nextStrips);
grow_buf_swap(&keptXy, &nextXy);
keptCount = nextCount;
}
void upscaler_submit(const struct retro_hw_upscaler_image& motion_vectors,
const struct retro_hw_upscaler_image& depth, unsigned flags)
{
if (upscaler == NULL || !requested)
return;
memset(&inputs, 0, sizeof(inputs));
inputs.flags = flags | (resetPending ? RETRO_HW_UPSCALER_RESET : 0);
inputs.motion_vectors = motion_vectors;
inputs.depth = depth;
upscaler_jitter(&inputs.jitter_x, &inputs.jitter_y);
inputs.mv_scale_x = 1.f;
inputs.mv_scale_y = 1.f;
inputs.pre_exposure = 1.f;
inputs.exposure_scale = 1.f;
inputs.frame_time_ms = 1000.f / 60.f;
upscaler->set_inputs(upscaler->handle, &inputs);
resetPending = false;
jitterIndex++;
}
void upscaler_reset()
{
resetPending = true;
jitterIndex = 0;
}