Files
melonDS/src/frontend/qt_sdl/Screen.cpp
2024-02-20 12:30:19 +01:00

1097 lines
28 KiB
C++

/*
Copyright 2016-2023 melonDS team
This file is part of melonDS.
melonDS 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 3 of the License, or (at your option)
any later version.
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <optional>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <QPaintEvent>
#include <QPainter>
#include <QDebug>
#ifndef _WIN32
#ifndef APPLE
#include <qpa/qplatformnativeinterface.h>
#endif
#endif
#include <QDateTime>
#include "OpenGLSupport.h"
#include "duckstation/gl/context.h"
#include "main.h"
#include "NDS.h"
#include "GPU.h"
#include "GPU3D_Soft.h"
#include "GPU3D_OpenGL.h"
#include "Platform.h"
#include "Config.h"
#include "main_shaders.h"
#include "OSD_shaders.h"
#include "font.h"
using namespace melonDS;
// TEMP
extern MainWindow* mainWindow;
extern EmuThread* emuThread;
extern bool RunningSomething;
extern int autoScreenSizing;
extern int videoRenderer;
extern bool videoSettingsDirty;
const u32 kOSDMargin = 6;
ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent)
{
setMouseTracking(true);
setAttribute(Qt::WA_AcceptTouchEvents);
QTimer* mouseTimer = setupMouseTimer();
connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) setCursor(Qt::BlankCursor);});
osdEnabled = false;
osdID = 1;
}
ScreenPanel::~ScreenPanel()
{
mouseTimer->stop();
delete mouseTimer;
}
void ScreenPanel::setupScreenLayout()
{
int w = width();
int h = height();
int sizing = Config::ScreenSizing;
if (sizing == 3) sizing = autoScreenSizing;
float aspectTop, aspectBot;
for (auto ratio : aspectRatios)
{
if (ratio.id == Config::ScreenAspectTop)
aspectTop = ratio.ratio;
if (ratio.id == Config::ScreenAspectBot)
aspectBot = ratio.ratio;
}
if (aspectTop == 0)
aspectTop = ((float) w / h) / (4.f / 3.f);
if (aspectBot == 0)
aspectBot = ((float) w / h) / (4.f / 3.f);
Frontend::SetupScreenLayout(w, h,
static_cast<Frontend::ScreenLayout>(Config::ScreenLayout),
static_cast<Frontend::ScreenRotation>(Config::ScreenRotation),
static_cast<Frontend::ScreenSizing>(sizing),
Config::ScreenGap,
Config::IntegerScaling != 0,
Config::ScreenSwap != 0,
aspectTop,
aspectBot);
numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind);
}
QSize ScreenPanel::screenGetMinSize(int factor = 1)
{
bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg
|| Config::ScreenRotation == Frontend::screenRot_270Deg);
int gap = Config::ScreenGap * factor;
int w = 256 * factor;
int h = 192 * factor;
if (Config::ScreenSizing == Frontend::screenSizing_TopOnly
|| Config::ScreenSizing == Frontend::screenSizing_BotOnly)
{
return QSize(w, h);
}
if (Config::ScreenLayout == Frontend::screenLayout_Natural)
{
if (isHori)
return QSize(h+gap+h, w);
else
return QSize(w, h+gap+h);
}
else if (Config::ScreenLayout == Frontend::screenLayout_Vertical)
{
if (isHori)
return QSize(h, w+gap+w);
else
return QSize(w, h+gap+h);
}
else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal)
{
if (isHori)
return QSize(h+gap+h, w);
else
return QSize(w+gap+w, h);
}
else // hybrid
{
if (isHori)
return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0));
else
return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h);
}
}
void ScreenPanel::onScreenLayoutChanged()
{
setMinimumSize(screenGetMinSize());
setupScreenLayout();
}
void ScreenPanel::resizeEvent(QResizeEvent* event)
{
setupScreenLayout();
QWidget::resizeEvent(event);
}
void ScreenPanel::mousePressEvent(QMouseEvent* event)
{
event->accept();
if (event->button() != Qt::LeftButton) return;
int x = event->pos().x();
int y = event->pos().y();
if (Frontend::GetTouchCoords(x, y, false))
{
touching = true;
assert(emuThread->NDS != nullptr);
emuThread->NDS->TouchScreen(x, y);
}
}
void ScreenPanel::mouseReleaseEvent(QMouseEvent* event)
{
event->accept();
if (event->button() != Qt::LeftButton) return;
if (touching)
{
touching = false;
assert(emuThread->NDS != nullptr);
emuThread->NDS->ReleaseScreen();
}
}
void ScreenPanel::mouseMoveEvent(QMouseEvent* event)
{
event->accept();
showCursor();
if (!(event->buttons() & Qt::LeftButton)) return;
if (!touching) return;
int x = event->pos().x();
int y = event->pos().y();
if (Frontend::GetTouchCoords(x, y, true))
{
assert(emuThread->NDS != nullptr);
emuThread->NDS->TouchScreen(x, y);
}
}
void ScreenPanel::tabletEvent(QTabletEvent* event)
{
event->accept();
switch(event->type())
{
case QEvent::TabletPress:
case QEvent::TabletMove:
{
int x = event->x();
int y = event->y();
if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove))
{
touching = true;
assert(emuThread->NDS != nullptr);
emuThread->NDS->TouchScreen(x, y);
}
}
break;
case QEvent::TabletRelease:
if (touching)
{
assert(emuThread->NDS != nullptr);
emuThread->NDS->ReleaseScreen();
touching = false;
}
break;
default:
break;
}
}
void ScreenPanel::touchEvent(QTouchEvent* event)
{
event->accept();
switch(event->type())
{
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
if (event->touchPoints().length() > 0)
{
QPointF lastPosition = event->touchPoints().first().lastPos();
int x = (int)lastPosition.x();
int y = (int)lastPosition.y();
if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate))
{
touching = true;
assert(emuThread->NDS != nullptr);
emuThread->NDS->TouchScreen(x, y);
}
}
break;
case QEvent::TouchEnd:
if (touching)
{
assert(emuThread->NDS != nullptr);
emuThread->NDS->ReleaseScreen();
touching = false;
}
break;
default:
break;
}
}
bool ScreenPanel::event(QEvent* event)
{
if (event->type() == QEvent::TouchBegin
|| event->type() == QEvent::TouchEnd
|| event->type() == QEvent::TouchUpdate)
{
touchEvent((QTouchEvent*)event);
return true;
}
return QWidget::event(event);
}
void ScreenPanel::showCursor()
{
mainWindow->panel->setCursor(Qt::ArrowCursor);
mouseTimer->start();
}
QTimer* ScreenPanel::setupMouseTimer()
{
mouseTimer = new QTimer();
mouseTimer->setSingleShot(true);
mouseTimer->setInterval(Config::MouseHideSeconds*1000);
mouseTimer->start();
return mouseTimer;
}
int ScreenPanel::osdFindBreakPoint(const char* text, int i)
{
// i = character that went out of bounds
for (int j = i; j >= 0; j--)
{
if (text[j] == ' ')
return j;
}
return i;
}
void ScreenPanel::osdLayoutText(const char* text, int* width, int* height, int* breaks)
{
int w = 0;
int h = 14;
int totalw = 0;
int maxw = ((QWidget*)this)->width() - (kOSDMargin*2);
int lastbreak = -1;
int numbrk = 0;
u16* ptr;
memset(breaks, 0, sizeof(int)*64);
for (int i = 0; text[i] != '\0'; )
{
int glyphsize;
if (text[i] == ' ')
{
glyphsize = 6;
}
else
{
u32 ch = text[i];
if (ch < 0x10 || ch > 0x7E) ch = 0x7F;
ptr = &::font[(ch-0x10) << 4];
glyphsize = ptr[0];
if (!glyphsize) glyphsize = 6;
else glyphsize += 2; // space around the character
}
w += glyphsize;
if (w > maxw)
{
// wrap shit as needed
if (text[i] == ' ')
{
if (numbrk >= 64) break;
breaks[numbrk++] = i;
i++;
}
else
{
int brk = osdFindBreakPoint(text, i);
if (brk != lastbreak) i = brk;
if (numbrk >= 64) break;
breaks[numbrk++] = i;
lastbreak = brk;
}
w = 0;
h += 14;
}
else
i++;
if (w > totalw) totalw = w;
}
*width = totalw;
*height = h;
}
unsigned int ScreenPanel::osdRainbowColor(int inc)
{
// inspired from Acmlmboard
if (inc < 100) return 0xFFFF9B9B + (inc << 8);
else if (inc < 200) return 0xFFFFFF9B - ((inc-100) << 16);
else if (inc < 300) return 0xFF9BFF9B + (inc-200);
else if (inc < 400) return 0xFF9BFFFF - ((inc-300) << 8);
else if (inc < 500) return 0xFF9B9BFF + ((inc-400) << 16);
else return 0xFFFF9BFF - (inc-500);
}
void ScreenPanel::osdRenderItem(OSDItem* item)
{
int w, h;
int breaks[64];
char* text = item->text;
u32 color = item->color;
bool rainbow = (color == 0);
u32 ticks = (u32)QDateTime::currentMSecsSinceEpoch();
u32 rainbowinc = ((text[0] * 17) + (ticks * 13)) % 600;
color |= 0xFF000000;
const u32 shadow = 0xE0000000;
osdLayoutText(text, &w, &h, breaks);
item->bitmap = QImage(w, h, QImage::Format_ARGB32_Premultiplied);
u32* bitmap = (u32*)item->bitmap.bits();
memset(bitmap, 0, w*h*sizeof(u32));
int x = 0, y = 1;
u32 maxw = ((QWidget*)this)->width() - (kOSDMargin*2);
int curline = 0;
u16* ptr;
for (int i = 0; text[i] != '\0'; )
{
int glyphsize;
if (text[i] == ' ')
{
x += 6;
}
else
{
u32 ch = text[i];
if (ch < 0x10 || ch > 0x7E) ch = 0x7F;
ptr = &::font[(ch-0x10) << 4];
int glyphsize = ptr[0];
if (!glyphsize) x += 6;
else
{
x++;
if (rainbow)
{
color = osdRainbowColor(rainbowinc);
rainbowinc = (rainbowinc + 30) % 600;
}
// draw character
for (int cy = 0; cy < 12; cy++)
{
u16 val = ptr[4+cy];
for (int cx = 0; cx < glyphsize; cx++)
{
if (val & (1<<cx))
bitmap[((y+cy) * w) + x+cx] = color;
}
}
x += glyphsize;
x++;
}
}
i++;
if (breaks[curline] && i >= breaks[curline])
{
i = breaks[curline++];
if (text[i] == ' ') i++;
x = 0;
y += 14;
}
}
// shadow
for (y = 0; y < h; y++)
{
for (x = 0; x < w; x++)
{
u32 val;
val = bitmap[(y * w) + x];
if ((val >> 24) == 0xFF) continue;
if (x > 0) val = bitmap[(y * w) + x-1];
if (x < w-1) val |= bitmap[(y * w) + x+1];
if (y > 0)
{
if (x > 0) val |= bitmap[((y-1) * w) + x-1];
val |= bitmap[((y-1) * w) + x];
if (x < w-1) val |= bitmap[((y-1) * w) + x+1];
}
if (y < h-1)
{
if (x > 0) val |= bitmap[((y+1) * w) + x-1];
val |= bitmap[((y+1) * w) + x];
if (x < w-1) val |= bitmap[((y+1) * w) + x+1];
}
if ((val >> 24) == 0xFF)
bitmap[(y * w) + x] = shadow;
}
}
}
void ScreenPanel::osdDeleteItem(OSDItem* item)
{
}
void ScreenPanel::osdSetEnabled(bool enabled)
{
osdMutex.lock();
osdEnabled = enabled;
osdMutex.unlock();
}
void ScreenPanel::osdAddMessage(unsigned int color, const char* text)
{
if (!osdEnabled) return;
osdMutex.lock();
OSDItem item;
item.id = osdID++;
item.timestamp = QDateTime::currentMSecsSinceEpoch();
strncpy(item.text, text, 255); item.text[255] = '\0';
item.color = color;
item.rendered = false;
osdItems.push_back(item);
osdMutex.unlock();
}
void ScreenPanel::osdUpdate()
{
osdMutex.lock();
qint64 tick_now = QDateTime::currentMSecsSinceEpoch();
qint64 tick_min = tick_now - 2500;
for (auto it = osdItems.begin(); it != osdItems.end(); )
{
OSDItem& item = *it;
if ((!osdEnabled) || (item.timestamp < tick_min))
{
osdDeleteItem(&item);
it = osdItems.erase(it);
continue;
}
if (!item.rendered)
{
osdRenderItem(&item);
item.rendered = true;
}
it++;
}
osdMutex.unlock();
}
ScreenPanelNative::ScreenPanelNative(QWidget* parent) : ScreenPanel(parent)
{
screen[0] = QImage(256, 192, QImage::Format_RGB32);
screen[1] = QImage(256, 192, QImage::Format_RGB32);
screenTrans[0].reset();
screenTrans[1].reset();
}
ScreenPanelNative::~ScreenPanelNative()
{
}
void ScreenPanelNative::setupScreenLayout()
{
ScreenPanel::setupScreenLayout();
for (int i = 0; i < numScreens; i++)
{
float* mtx = screenMatrix[i];
screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f,
mtx[2], mtx[3], 0.f,
mtx[4], mtx[5], 1.f);
}
}
void ScreenPanelNative::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
// fill background
painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0));
if (emuThread->emuIsActive())
{
assert(emuThread->NDS != nullptr);
emuThread->FrontBufferLock.lock();
int frontbuf = emuThread->FrontBuffer;
if (!emuThread->NDS->GPU.Framebuffer[frontbuf][0] || !emuThread->NDS->GPU.Framebuffer[frontbuf][1])
{
emuThread->FrontBufferLock.unlock();
return;
}
memcpy(screen[0].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4);
memcpy(screen[1].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4);
emuThread->FrontBufferLock.unlock();
QRect screenrc(0, 0, 256, 192);
for (int i = 0; i < numScreens; i++)
{
painter.setTransform(screenTrans[i]);
painter.drawImage(screenrc, screen[screenKind[i]]);
}
}
osdUpdate();
if (osdEnabled)
{
osdMutex.lock();
u32 y = kOSDMargin;
painter.resetTransform();
for (auto it = osdItems.begin(); it != osdItems.end(); )
{
OSDItem& item = *it;
painter.drawImage(kOSDMargin, y, item.bitmap);
y += item.bitmap.height();
it++;
}
osdMutex.unlock();
}
}
ScreenPanelGL::ScreenPanelGL(QWidget* parent) : ScreenPanel(parent)
{
setAutoFillBackground(false);
setAttribute(Qt::WA_NativeWindow, true);
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_PaintOnScreen, true);
setAttribute(Qt::WA_KeyCompression, false);
setFocusPolicy(Qt::StrongFocus);
setMinimumSize(screenGetMinSize());
}
ScreenPanelGL::~ScreenPanelGL()
{}
GL::Context* hax = nullptr;
bool ScreenPanelGL::createContext()
{
std::optional<WindowInfo> windowInfo = getWindowInfo();
if (hax)
{
if (windowInfo.has_value())
{
glContext = hax->CreateSharedContext(*getWindowInfo());
glContext->DoneCurrent();
}
return glContext != nullptr;
}
std::array<GL::Context::Version, 2> versionsToTry = {
GL::Context::Version{GL::Context::Profile::Core, 4, 3},
GL::Context::Version{GL::Context::Profile::Core, 3, 2}};
if (windowInfo.has_value())
{
glContext = GL::Context::Create(*getWindowInfo(), versionsToTry);
glContext->DoneCurrent();
hax = glContext.get();
}
return glContext != nullptr;
}
void ScreenPanelGL::setSwapInterval(int intv)
{
if (!glContext) return;
glContext->SetSwapInterval(intv);
}
void ScreenPanelGL::initOpenGL()
{
if (!glContext) return;
glContext->MakeCurrent();
OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader");
GLuint pid = screenShaderProgram[2];
glBindAttribLocation(pid, 0, "vPosition");
glBindAttribLocation(pid, 1, "vTexcoord");
glBindFragDataLocation(pid, 0, "oColor");
OpenGL::LinkShaderProgram(screenShaderProgram);
glUseProgram(pid);
glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0);
screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize");
screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform");
// to prevent bleeding between both parts of the screen
// with bilinear filtering enabled
const int paddedHeight = 192*2+2;
const float padPixels = 1.f / paddedHeight;
const float vertices[] =
{
0.f, 0.f, 0.f, 0.f,
0.f, 192.f, 0.f, 0.5f - padPixels,
256.f, 192.f, 1.f, 0.5f - padPixels,
0.f, 0.f, 0.f, 0.f,
256.f, 192.f, 1.f, 0.5f - padPixels,
256.f, 0.f, 1.f, 0.f,
0.f, 0.f, 0.f, 0.5f + padPixels,
0.f, 192.f, 0.f, 1.f,
256.f, 192.f, 1.f, 1.f,
0.f, 0.f, 0.f, 0.5f + padPixels,
256.f, 192.f, 1.f, 1.f,
256.f, 0.f, 1.f, 0.5f + padPixels
};
glGenBuffers(1, &screenVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &screenVertexArray);
glBindVertexArray(screenVertexArray);
glEnableVertexAttribArray(0); // position
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0));
glEnableVertexAttribArray(1); // texcoord
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4));
glGenTextures(1, &screenTexture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, screenTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
// fill the padding
u8 zeroData[256*4*4];
memset(zeroData, 0, sizeof(zeroData));
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData);
OpenGL::BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, osdShader, "OSDShader");
pid = osdShader[2];
glBindAttribLocation(pid, 0, "vPosition");
glBindFragDataLocation(pid, 0, "oColor");
OpenGL::LinkShaderProgram(osdShader);
glUseProgram(pid);
glUniform1i(glGetUniformLocation(pid, "OSDTex"), 0);
osdScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize");
osdPosULoc = glGetUniformLocation(pid, "uOSDPos");
osdSizeULoc = glGetUniformLocation(pid, "uOSDSize");
osdScaleFactorULoc = glGetUniformLocation(pid, "uScaleFactor");
const float osdvertices[6*2] =
{
0, 0,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1
};
glGenBuffers(1, &osdVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(osdvertices), osdvertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &osdVertexArray);
glBindVertexArray(osdVertexArray);
glEnableVertexAttribArray(0); // position
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0));
glContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0);
transferLayout();
}
void ScreenPanelGL::deinitOpenGL()
{
if (!glContext) return;
glDeleteTextures(1, &screenTexture);
glDeleteVertexArrays(1, &screenVertexArray);
glDeleteBuffers(1, &screenVertexBuffer);
OpenGL::DeleteShaderProgram(screenShaderProgram);
for (const auto& [key, tex] : osdTextures)
{
glDeleteTextures(1, &tex);
}
osdTextures.clear();
glDeleteVertexArrays(1, &osdVertexArray);
glDeleteBuffers(1, &osdVertexBuffer);
OpenGL::DeleteShaderProgram(osdShader);
glContext->DoneCurrent();
lastScreenWidth = lastScreenHeight = -1;
}
void ScreenPanelGL::makeCurrentGL()
{
if (!glContext) return;
glContext->MakeCurrent();
}
void ScreenPanelGL::osdRenderItem(OSDItem* item)
{
ScreenPanel::osdRenderItem(item);
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, item->bitmap.width(), item->bitmap.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, item->bitmap.bits());
osdTextures[item->id] = tex;
}
void ScreenPanelGL::osdDeleteItem(OSDItem* item)
{
if (osdTextures.count(item->id))
{
GLuint tex = osdTextures[item->id];
glDeleteTextures(1, &tex);
osdTextures.erase(item->id);
}
ScreenPanel::osdDeleteItem(item);
}
void ScreenPanelGL::drawScreenGL()
{
if (!glContext) return;
if (!emuThread->NDS) return;
glContext->MakeCurrent();
int w = windowInfo.surface_width;
int h = windowInfo.surface_height;
float factor = windowInfo.surface_scale;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glDepthMask(false);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_STENCIL_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, w, h);
glUseProgram(screenShaderProgram[2]);
glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor);
int frontbuf = emuThread->FrontBuffer;
glActiveTexture(GL_TEXTURE0);
#ifdef OGLRENDERER_ENABLED
if (emuThread->NDS->GPU.GetRenderer3D().Accelerated)
{
// hardware-accelerated render
static_cast<GLRenderer&>(emuThread->NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf);
}
else
#endif
{
// regular render
glBindTexture(GL_TEXTURE_2D, screenTexture);
if (emuThread->NDS->GPU.Framebuffer[frontbuf][0] && emuThread->NDS->GPU.Framebuffer[frontbuf][1])
{
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA,
GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][0].get());
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA,
GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][1].get());
}
}
screenSettingsLock.lock();
GLint filter = this->filter ? GL_LINEAR : GL_NEAREST;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer);
glBindVertexArray(screenVertexArray);
for (int i = 0; i < numScreens; i++)
{
glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]);
glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3);
}
screenSettingsLock.unlock();
osdUpdate();
if (osdEnabled)
{
osdMutex.lock();
u32 y = kOSDMargin;
glUseProgram(osdShader[2]);
glUniform2f(osdScreenSizeULoc, w, h);
glUniform1f(osdScaleFactorULoc, factor);
glBindBuffer(GL_ARRAY_BUFFER, osdVertexBuffer);
glBindVertexArray(osdVertexArray);
glActiveTexture(GL_TEXTURE0);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
for (auto it = osdItems.begin(); it != osdItems.end(); )
{
OSDItem& item = *it;
if (!osdTextures.count(item.id))
continue;
glBindTexture(GL_TEXTURE_2D, osdTextures[item.id]);
glUniform2i(osdPosULoc, kOSDMargin, y);
glUniform2i(osdSizeULoc, item.bitmap.width(), item.bitmap.height());
glDrawArrays(GL_TRIANGLES, 0, 2*3);
y += item.bitmap.height();
it++;
}
glDisable(GL_BLEND);
glUseProgram(0);
osdMutex.unlock();
}
glContext->SwapBuffers();
}
qreal ScreenPanelGL::devicePixelRatioFromScreen() const
{
const QScreen* screen_for_ratio = window()->windowHandle()->screen();
if (!screen_for_ratio)
screen_for_ratio = QGuiApplication::primaryScreen();
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
}
int ScreenPanelGL::scaledWindowWidth() const
{
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioFromScreen())), 1);
}
int ScreenPanelGL::scaledWindowHeight() const
{
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioFromScreen())), 1);
}
std::optional<WindowInfo> ScreenPanelGL::getWindowInfo()
{
WindowInfo wi;
// Windows and Apple are easy here since there's no display connection.
#if defined(_WIN32)
wi.type = WindowInfo::Type::Win32;
wi.window_handle = reinterpret_cast<void*>(winId());
#elif defined(__APPLE__)
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = reinterpret_cast<void*>(winId());
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
const QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("xcb"))
{
wi.type = WindowInfo::Type::X11;
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
wi.window_handle = reinterpret_cast<void*>(winId());
}
else if (platform_name == QStringLiteral("wayland"))
{
wi.type = WindowInfo::Type::Wayland;
QWindow* handle = windowHandle();
if (handle == nullptr)
return std::nullopt;
wi.display_connection = pni->nativeResourceForWindow("display", handle);
wi.window_handle = pni->nativeResourceForWindow("surface", handle);
}
else
{
qCritical() << "Unknown PNI platform " << platform_name;
return std::nullopt;
}
#endif
wi.surface_width = static_cast<u32>(scaledWindowWidth());
wi.surface_height = static_cast<u32>(scaledWindowHeight());
wi.surface_scale = static_cast<float>(devicePixelRatioFromScreen());
return wi;
}
QPaintEngine* ScreenPanelGL::paintEngine() const
{
return nullptr;
}
void ScreenPanelGL::setupScreenLayout()
{
ScreenPanel::setupScreenLayout();
transferLayout();
}
void ScreenPanelGL::transferLayout()
{
std::optional<WindowInfo> windowInfo = getWindowInfo();
if (windowInfo.has_value())
{
screenSettingsLock.lock();
if (lastScreenWidth != windowInfo->surface_width || lastScreenHeight != windowInfo->surface_height)
{
if (glContext)
glContext->ResizeSurface(windowInfo->surface_width, windowInfo->surface_height);
lastScreenWidth = windowInfo->surface_width;
lastScreenHeight = windowInfo->surface_height;
}
this->filter = Config::ScreenFilter;
this->windowInfo = *windowInfo;
screenSettingsLock.unlock();
}
}