flycast/core/hw/pvr/Renderer_if.cpp

512 lines
10 KiB
C++

#include "Renderer_if.h"
#include "spg.h"
#include "rend/TexCache.h"
#include "rend/transform_matrix.h"
#include "cfg/option.h"
#include "emulator.h"
#include "serialize.h"
#include "hw/holly/holly_intc.h"
#include "hw/sh4/sh4_if.h"
#include "profiler/fc_profiler.h"
#include <mutex>
#include <deque>
#ifdef LIBRETRO
void retro_rend_present();
void retro_resize_renderer(int w, int h, float aspectRatio);
#endif
u32 FrameCount=1;
Renderer* renderer;
static cResetEvent renderEnd;
u32 fb_w_cur = 1;
static cResetEvent vramRollback;
// direct framebuffer write detection
static bool render_called = false;
u32 fb_watch_addr_start;
u32 fb_watch_addr_end;
bool fb_dirty;
static bool pend_rend;
TA_context* _pvrrc;
static bool presented;
class PvrMessageQueue
{
using lock_guard = std::lock_guard<std::mutex>;
public:
enum MessageType { NoMessage = -1, Render, RenderFramebuffer, Present, Stop };
struct Message
{
Message() = default;
Message(MessageType type, FramebufferInfo config)
: type(type), config(config) {}
MessageType type = NoMessage;
FramebufferInfo config;
};
void enqueue(MessageType type, FramebufferInfo config = FramebufferInfo())
{
Message msg { type, config };
if (config::ThreadedRendering)
{
// FIXME need some synchronization to avoid blinking in densha de go
// or use !threaded rendering for emufb?
// or read framebuffer vram on emu thread
bool dupe;
do {
dupe = false;
{
const lock_guard lock(mutex);
for (const auto& m : queue)
if (m.type == type) {
dupe = true;
break;
}
if (!dupe)
queue.push_back(msg);
}
if (dupe)
{
if (type == Stop)
return;
dequeueEvent.Wait();
}
} while (dupe);
enqueueEvent.Set();
}
else
{
// drain the queue after switching to !threaded rendering
while (!queue.empty())
waitAndExecute();
execute(msg);
}
}
bool waitAndExecute(int timeoutMs = -1)
{
return execute(dequeue(timeoutMs));
}
void reset() {
const lock_guard lock(mutex);
queue.clear();
}
void cancelEnqueue()
{
const lock_guard lock(mutex);
for (auto it = queue.begin(); it != queue.end(); )
{
if (it->type != Render)
it = queue.erase(it);
else
++it;
}
dequeueEvent.Set();
}
private:
Message dequeue(int timeoutMs = -1)
{
FC_PROFILE_SCOPE;
Message msg;
while (true)
{
{
const lock_guard lock(mutex);
if (!queue.empty())
{
msg = queue.front();
queue.pop_front();
}
}
if (msg.type != NoMessage) {
dequeueEvent.Set();
break;
}
if (timeoutMs == -1)
enqueueEvent.Wait();
else if (!enqueueEvent.Wait(timeoutMs))
break;
}
return msg;
}
bool execute(Message msg)
{
switch (msg.type)
{
case Render:
render();
return true;
case RenderFramebuffer:
renderFramebuffer(msg.config);
return true;
case Present:
present();
return true;
case Stop:
case NoMessage:
default:
return false;
}
}
void render()
{
FC_PROFILE_SCOPE;
_pvrrc = DequeueRender();
if (_pvrrc == nullptr)
return;
if (!_pvrrc->rend.isRTT)
{
int width, height;
getScaledFramebufferSize(_pvrrc->rend, width, height);
_pvrrc->rend.framebufferWidth = width;
_pvrrc->rend.framebufferHeight = height;
}
bool renderToScreen = !_pvrrc->rend.isRTT && !config::EmulateFramebuffer;
#ifdef LIBRETRO
if (renderToScreen)
retro_resize_renderer(_pvrrc->rend.framebufferWidth, _pvrrc->rend.framebufferHeight,
getOutputFramebufferAspectRatio());
#endif
{
FC_PROFILE_SCOPE_NAMED("Renderer::Process");
renderer->Process(_pvrrc);
}
if (renderToScreen)
// If rendering to texture or in full framebuffer emulation, continue locking until the frame is rendered
renderEnd.Set();
rend_allow_rollback();
{
FC_PROFILE_SCOPE_NAMED("Renderer::Render");
renderer->Render();
}
if (!renderToScreen)
renderEnd.Set();
else if (config::DelayFrameSwapping && fb_w_cur == FB_R_SOF1)
present();
//clear up & free data ..
FinishRender(_pvrrc);
_pvrrc = nullptr;
}
void renderFramebuffer(const FramebufferInfo& config)
{
FC_PROFILE_SCOPE;
#ifdef LIBRETRO
int w, h;
getDCFramebufferReadSize(config, w, h);
retro_resize_renderer(w, h, getDCFramebufferAspectRatio());
#endif
renderer->RenderFramebuffer(config);
}
void present()
{
FC_PROFILE_SCOPE;
if (renderer->Present())
{
presented = true;
if (!config::ThreadedRendering)
sh4_cpu.Stop();
#ifdef LIBRETRO
retro_rend_present();
#endif
}
}
std::mutex mutex;
cResetEvent enqueueEvent;
cResetEvent dequeueEvent;
std::deque<Message> queue;
};
static PvrMessageQueue pvrQueue;
bool rend_single_frame(const bool& enabled)
{
FC_PROFILE_SCOPE;
presented = false;
while (enabled && !presented)
if (!pvrQueue.waitAndExecute(50))
return false;
return true;
}
Renderer* rend_GLES2();
Renderer* rend_GL4();
Renderer* rend_norend();
Renderer* rend_Vulkan();
Renderer* rend_OITVulkan();
Renderer* rend_DirectX9();
Renderer* rend_DirectX11();
Renderer* rend_OITDirectX11();
static void rend_create_renderer()
{
#ifdef NO_REND
renderer = rend_norend();
#else
switch (config::RendererType)
{
default:
#ifdef USE_OPENGL
case RenderType::OpenGL:
renderer = rend_GLES2();
break;
#if !defined(GLES) && !defined(__APPLE__)
case RenderType::OpenGL_OIT:
renderer = rend_GL4();
break;
#endif
#endif
#ifdef USE_VULKAN
case RenderType::Vulkan:
renderer = rend_Vulkan();
break;
case RenderType::Vulkan_OIT:
renderer = rend_OITVulkan();
break;
#endif
#ifdef USE_DX9
case RenderType::DirectX9:
renderer = rend_DirectX9();
break;
#endif
#ifdef USE_DX11
case RenderType::DirectX11:
renderer = rend_DirectX11();
break;
case RenderType::DirectX11_OIT:
renderer = rend_OITDirectX11();
break;
#endif
}
#endif
}
bool rend_init_renderer()
{
if (renderer == nullptr)
rend_create_renderer();
bool success = renderer->Init();
if (!success) {
delete renderer;
renderer = rend_norend();
renderer->Init();
}
return success;
}
void rend_term_renderer()
{
if (renderer != nullptr)
{
renderer->Term();
delete renderer;
renderer = nullptr;
}
}
void rend_reset()
{
FinishRender(DequeueRender());
render_called = false;
pend_rend = false;
FrameCount = 1;
fb_w_cur = 1;
pvrQueue.reset();
}
void rend_start_render()
{
render_called = true;
pend_rend = false;
TA_context *ctx = nullptr;
u32 addresses[MAX_PASSES];
int count = getTAContextAddresses(addresses);
if (count > 0)
{
ctx = tactx_Pop(addresses[0]);
if (ctx != nullptr)
{
TA_context *linkedCtx = ctx;
for (int i = 1; i < count; i++)
{
linkedCtx->nextContext = tactx_Pop(addresses[i]);
if (linkedCtx->nextContext != nullptr)
linkedCtx = linkedCtx->nextContext;
else
INFO_LOG(PVR, "rend_start_render: Context%d @ %x not found", i, addresses[i]);
}
}
else
INFO_LOG(PVR, "rend_start_render: Context0 @ %x not found", addresses[0]);
}
else
INFO_LOG(PVR, "rend_start_render: No context not found");
scheduleRenderDone(ctx);
if (ctx == nullptr)
return;
FillBGP(ctx);
ctx->rend.isRTT = (FB_W_SOF1 & 0x1000000) != 0;
ctx->rend.fb_W_SOF1 = FB_W_SOF1;
ctx->rend.fb_W_CTRL.full = FB_W_CTRL.full;
ctx->rend.ta_GLOB_TILE_CLIP = TA_GLOB_TILE_CLIP;
ctx->rend.scaler_ctl = SCALER_CTL;
ctx->rend.fb_X_CLIP = FB_X_CLIP;
ctx->rend.fb_Y_CLIP = FB_Y_CLIP;
ctx->rend.fb_W_LINESTRIDE = FB_W_LINESTRIDE.stride;
ctx->rend.fog_clamp_min = FOG_CLAMP_MIN;
ctx->rend.fog_clamp_max = FOG_CLAMP_MAX;
if (QueueRender(ctx))
{
palette_update();
pend_rend = true;
pvrQueue.enqueue(PvrMessageQueue::Render);
if (!config::DelayFrameSwapping && !ctx->rend.isRTT && !config::EmulateFramebuffer)
pvrQueue.enqueue(PvrMessageQueue::Present);
}
}
int rend_end_render(int tag, int cycles, int jitter)
{
if (settings.platform.isNaomi2())
{
asic_RaiseInterruptBothCLX(holly_RENDER_DONE);
asic_RaiseInterruptBothCLX(holly_RENDER_DONE_isp);
asic_RaiseInterruptBothCLX(holly_RENDER_DONE_vd);
}
else
{
asic_RaiseInterrupt(holly_RENDER_DONE);
asic_RaiseInterrupt(holly_RENDER_DONE_isp);
asic_RaiseInterrupt(holly_RENDER_DONE_vd);
}
if (pend_rend && config::ThreadedRendering)
renderEnd.Wait();
return 0;
}
void rend_vblank()
{
if (config::EmulateFramebuffer
|| (!render_called && fb_dirty && FB_R_CTRL.fb_enable))
{
FramebufferInfo fbInfo;
fbInfo.update();
pvrQueue.enqueue(PvrMessageQueue::RenderFramebuffer, fbInfo);
pvrQueue.enqueue(PvrMessageQueue::Present);
if (!config::EmulateFramebuffer)
DEBUG_LOG(PVR, "Direct framebuffer write detected");
fb_dirty = false;
}
render_called = false;
check_framebuffer_write();
emu.vblank();
}
void check_framebuffer_write()
{
u32 fb_size = (FB_R_SIZE.fb_y_size + 1) * (FB_R_SIZE.fb_x_size + FB_R_SIZE.fb_modulus) * 4;
fb_watch_addr_start = (SPG_CONTROL.interlace ? FB_R_SOF2 : FB_R_SOF1) & VRAM_MASK;
fb_watch_addr_end = fb_watch_addr_start + fb_size;
}
void rend_cancel_emu_wait()
{
if (config::ThreadedRendering)
{
FinishRender(NULL);
renderEnd.Set();
rend_allow_rollback();
pvrQueue.cancelEnqueue();
// Needed for android where this function may be called
// from a thread different from the UI one
pvrQueue.enqueue(PvrMessageQueue::Stop);
}
}
void rend_set_fb_write_addr(u32 fb_w_sof1)
{
if (fb_w_sof1 & 0x1000000)
// render to texture
return;
fb_w_cur = fb_w_sof1;
}
void rend_swap_frame(u32 fb_r_sof)
{
if (!config::EmulateFramebuffer && fb_r_sof == fb_w_cur)
pvrQueue.enqueue(PvrMessageQueue::Present);
}
void rend_disable_rollback()
{
vramRollback.Reset();
}
void rend_allow_rollback()
{
vramRollback.Set();
}
void rend_start_rollback()
{
if (config::ThreadedRendering)
vramRollback.Wait();
}
void rend_serialize(Serializer& ser)
{
ser << fb_w_cur;
ser << render_called;
ser << fb_dirty;
ser << fb_watch_addr_start;
ser << fb_watch_addr_end;
}
void rend_deserialize(Deserializer& deser)
{
if ((deser.version() >= Deserializer::V12_LIBRETRO && deser.version() < Deserializer::V5) || deser.version() >= Deserializer::V12)
deser >> fb_w_cur;
else
fb_w_cur = 1;
if (deser.version() >= Deserializer::V20)
{
deser >> render_called;
deser >> fb_dirty;
deser >> fb_watch_addr_start;
deser >> fb_watch_addr_end;
}
pend_rend = false;
}