Compare commits

...

2 commits

Author SHA1 Message Date
rdanbrook e82ad38035 FLTK: Add log driver 2024-05-27 23:31:46 -06:00
rdanbrook 412b78681c FLTK: Rewrite video code and do some random cleanup elsewhere 2024-05-27 23:05:10 -06:00
10 changed files with 223 additions and 209 deletions

View file

@ -718,12 +718,12 @@ nestopia_SOURCES += \
source/fltkui/chtmanager.h \
source/fltkui/cli.cpp \
source/fltkui/cli.h \
source/fltkui/video.cpp \
source/fltkui/video.h \
source/fltkui/ini.h \
source/fltkui/inputmanager.cpp \
source/fltkui/inputmanager.h \
source/fltkui/font.h \
source/fltkui/logdriver.cpp \
source/fltkui/logdriver.h \
source/fltkui/png.cpp \
source/fltkui/png.h \
source/fltkui/jgmanager.cpp \
@ -732,6 +732,8 @@ nestopia_SOURCES += \
source/fltkui/setmanager.h \
source/fltkui/uiadapter.cpp \
source/fltkui/uiadapter.h \
source/fltkui/videomanager.cpp \
source/fltkui/videomanager.h \
source/fltkui/jg.cpp \
source/fltkui/jg/jg.h \
source/fltkui/jg/jg_nes.h

View file

@ -182,8 +182,8 @@ void AudioManager::queue(size_t in_size) {
for (int i = 0; i < srcdata.output_frames_gen; i++) {
fltbuf_out[i] *= 32768;
buf_out[bufend] = fltbuf_out[i] >= 32767.0 ? 32767 :
fltbuf_out[i] <= -32768.0 ? -32768 :
fltbuf_out[i];
fltbuf_out[i] <= -32768.0 ? -32768 :
fltbuf_out[i];
bufend = (bufend + 1) % BUFSIZE;
bufsamples++;
if (bufsamples >= BUFSIZE - 1) {

View file

@ -45,8 +45,7 @@
#include "inputmanager.h"
#include "jgmanager.h"
#include "setmanager.h"
#include "video.h"
#include "videomanager.h"
#include "fltkui.h"
#include "fltkui_archive.h"
@ -69,6 +68,7 @@ JGManager *jgm{nullptr};
SettingManager *setmgr{nullptr};
InputManager *inputmgr{nullptr};
AudioManager *audiomgr{nullptr};
VideoManager *videomgr{nullptr};
CheatManager *chtmgr{nullptr};
std::vector<uint8_t> game;
@ -123,7 +123,7 @@ static void fltkui_load_file(const char *filename) {
jgm->load_game(arcname.c_str(), game);
}
else {
nst_video_print("No valid files in archive", 8, 212, 2, true);
VideoManager::text_print("No valid files in archive", 8, 212, 2, true);
}
}
else {
@ -281,7 +281,7 @@ static void fltkui_screenshot(Fl_Widget* w, void* userdata) {
return;
}
video_screenshot(fc.filename());
//video_screenshot(fc.filename());
}
static void fltkui_palette_open(Fl_Widget* w, void* userdata) {
@ -353,11 +353,11 @@ void NstWindow::resize(int x, int y, int w, int h) {
void NstGlArea::resize(int x, int y, int w, int h) {
Fl_Window::resize(x, y, w, h);
nst_video_resize(w, h);
videomgr->resize(w, h);
}
void FltkUi::rehash() {
nst_video_rehash();
videomgr->rehash();
audiomgr->rehash();
}
@ -369,14 +369,10 @@ void FltkUi::fullscreen(Fl_Widget *w, void *data) {
video_fullscreen ^= 1;
if (video_fullscreen) {
int x, y, w, h;
Fl::screen_xywh(x, y, w, h);
menubar->hide();
nstwin->fullscreen();
}
else {
int rw, rh;
nst_video_dimensions(&rw, &rh);
nstwin->fullscreen_off();
menubar->show();
}
@ -463,7 +459,7 @@ int NstWindow::handle(int e) {
}
void NstGlArea::draw() {
nst_ogl_render();
videomgr->ogl_render();
}
int NstGlArea::handle(int e) {
@ -487,11 +483,11 @@ int NstGlArea::handle(int e) {
inputmgr->event(Fl::event_button() + 1000, false);
break;
case FL_MOVE:
video_scaled_coords(Fl::event_x(), Fl::event_y(), &xc, &yc);
videomgr->get_scaled_coords(Fl::event_x(), Fl::event_y(), &xc, &yc);
inputmgr->event(xc, yc);
break;
case FL_DRAG:
video_scaled_coords(Fl::event_x(), Fl::event_y(), &xc, &yc);
videomgr->get_scaled_coords(Fl::event_x(), Fl::event_y(), &xc, &yc);
inputmgr->event(xc, yc);
inputmgr->event(Fl::event_button() + 1000, Fl::event_state() ? true : false);
break;
@ -554,7 +550,7 @@ void FltkUi::show_msgbox(bool show) {
void makenstwin(const char *name) {
int rw, rh;
nst_video_dimensions(&rw, &rh);
videomgr->get_dimensions(&rw, &rh);
Fl::add_handler(handle);
@ -613,10 +609,9 @@ int main(int argc, char *argv[]) {
// Read frontend and emulator settings
setmgr->read(*jgm);
// Bring up Audio/Video managers
audiomgr = new AudioManager(*jgm, *setmgr);
// Initialize video params
video_init(setmgr, jgm);
videomgr = new VideoManager(*jgm, *setmgr);
// Set archive handler function pointer
//nst_archive_select = &fltkui_archive_select;
@ -632,7 +627,7 @@ int main(int argc, char *argv[]) {
glarea->show();
Fl::check();
nst_ogl_init();
videomgr->ogl_init();
// Load a rom from the command line
if (argc > 1 && argv[argc - 1][0] != '-') {
@ -694,6 +689,10 @@ int main(int argc, char *argv[]) {
delete audiomgr;
}
if (videomgr) {
delete videomgr;
}
if (inputmgr) {
delete inputmgr;
}

View file

@ -20,7 +20,6 @@
*
*/
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <filesystem>
@ -30,7 +29,7 @@
#include "jgmanager.h"
#include "video.h" // FIXME - move this
#include "logdriver.h"
namespace {
@ -40,32 +39,6 @@ void jg_frametime(double interval) {
frametime = interval + 0.5;
}
void jg_log(int level, const char *fmt, ...) {
va_list va;
char buffer[512];
static const char *lcol[4] = {
"\033[0;35m", "\033[0;36m", "\033[7;33m", "\033[1;7;31m"
};
va_start(va, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, va);
va_end(va);
FILE *fout = level == 1 ? stdout : stderr;
if (level == JG_LOG_SCR) {
nst_video_print(buffer, 8, 212, 2, true);
return;
}
fprintf(fout, "%s%s\033[0m", lcol[level], buffer);
fflush(fout);
if (level == JG_LOG_ERR) {
nst_video_print(buffer, 8, 212, 2, true);
}
}
} // namespace
JGManager::JGManager() {
@ -78,7 +51,7 @@ JGManager::JGManager() {
}
jg_set_cb_frametime(jg_frametime);
jg_set_cb_log(&jg_log);
jg_set_cb_log(&LogDriver::jg_log);
jg_init();
}
@ -211,9 +184,9 @@ int JGManager::state_qload(int slot) {
int result = state_load(slotpath);
switch (result) {
case 0: jg_log(JG_LOG_SCR, "State Load Failed", 8, 212, 2, true); break;
case 1: jg_log(JG_LOG_SCR, "State Loaded", 8, 212, 2, true); break;
default: jg_log(JG_LOG_SCR, "State Load Unknown", 8, 212, 2, true); break;
case 0: LogDriver::jg_log(JG_LOG_SCR, "State Load Failed"); break;
case 1: LogDriver::jg_log(JG_LOG_SCR, "State Loaded"); break;
default: LogDriver::jg_log(JG_LOG_SCR, "State Load Unknown"); break;
}
return result;
@ -237,9 +210,9 @@ int JGManager::state_qsave(int slot) {
int result = state_save(slotpath);
switch (result) {
case 0: jg_log(JG_LOG_SCR, "State Save Failed", 8, 212, 2, true); break;
case 1: jg_log(JG_LOG_SCR, "State Saved", 8, 212, 2, true); break;
default: jg_log(JG_LOG_SCR, "State Save Unknown", 8, 212, 2, true); break;
case 0: LogDriver::jg_log(JG_LOG_SCR, "State Save Failed"); break;
case 1: LogDriver::jg_log(JG_LOG_SCR, "State Saved"); break;
default: LogDriver::jg_log(JG_LOG_SCR, "State Save Unknown"); break;
}
return result;

View file

@ -0,0 +1,51 @@
/*
* Nestopia UE
*
* Copyright (C) 2012-2024 R. Danbrook
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include "videomanager.h"
#include "logdriver.h"
void LogDriver::jg_log(int level, const char *fmt, ...) {
va_list va;
char buffer[512];
static const char *lcol[4] = {
"\033[0;35m", "\033[0;36m", "\033[7;33m", "\033[1;7;31m"
};
va_start(va, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, va);
va_end(va);
FILE *fout = level == 1 ? stdout : stderr;
if (level == JG_LOG_SCR) {
VideoManager::text_print(buffer, 8, 212, 2, true);
return;
}
fprintf(fout, "%s%s\033[0m", lcol[level], buffer);
fflush(fout);
if (level == JG_LOG_ERR) {
VideoManager::text_print(buffer, 8, 212, 2, true);
}
}

View file

@ -0,0 +1,8 @@
#pragma once
#include <cstdarg>
class LogDriver { // The Log Driver's Waltz pleases girls completely
public:
static void jg_log(int level, const char *fmt, ...);
};

View file

@ -27,12 +27,12 @@
#include <iostream>
#include <iterator>
#include "setmanager.h"
#include <jg/jg.h>
#include <jg/jg_nes.h>
#include "ini.h"
#include "video.h" // Use UiAdapter for this...
#include "setmanager.h"
namespace {

View file

@ -1,36 +0,0 @@
#pragma once
#include "setmanager.h"
#include "jgmanager.h"
typedef struct {
int xpos;
int ypos;
char textbuf[32];
char timebuf[6];
int drawtext;
bool drawtime;
bool bg;
} osdtext_t;
void nst_ogl_init();
void nst_ogl_deinit();
void nst_ogl_render();
void nst_video_rehash();
void nst_video_resize(int w, int h);
void video_init(SettingManager *s, JGManager *j);
void video_scaled_coords(int x, int y, int *xcoord, int *ycoord);
void video_screenshot(const char* filename);
void video_clear_buffer();
void video_disp_nsf();
void nst_video_print(const char *text, int xpos, int ypos, int seconds, bool bg);
void nst_video_print_time(const char *timebuf, bool drawtime);
void nst_video_text_draw(const char *text, int xpos, int ypos, bool bg);
void nst_video_text_match(const char *text, int *xpos, int *ypos, int strpos);
void nst_video_dimensions(int *w, int *h);

View file

@ -20,68 +20,65 @@
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <cstdint>
#include <cstring>
#include <FL/gl.h>
#include "video.h"
#include "videomanager.h"
#include "font.h"
#include "png.h"
static uint32_t videobuf[602 * 240]; // FIXME hardcoded
namespace {
static osdtext_t osdtext;
struct osdtext {
int xpos;
int ypos;
char textbuf[32];
char timebuf[6];
int drawtext;
bool drawtime;
bool bg;
} osdtext;
static jg_videoinfo_t* vidinfo;
jg_videoinfo_t *vidinfo;
uint32_t *videobuf;
static GLuint gl_texture_id = 0;
static double aspect = 1.0;
static SettingManager *setmgr;
static JGManager *jgm;
// Triangle and Texture vertices
static GLfloat vertices[] = {
-1.0, -1.0, // Vertex 1 (X, Y) Left Bottom
-1.0, 1.0, // Vertex 2 (X, Y) Left Top
1.0, -1.0, // Vertex 3 (X, Y) Right Bottom
1.0, 1.0, // Vertex 4 (X, Y) Right Top
0.0, 0.0, // Texture 2 (X, Y) Left Top
0.0, 1.0, // Texture 1 (X, Y) Left Bottom
1.0, 0.0, // Texture 4 (X, Y) Right Top
1.0, 1.0, // Texture 3 (X, Y) Right Bottom
};
// Dimensions
static struct _dimensions {
int ww; int wh;
float rw; float rh;
float xo; float yo;
float dpiscale;
} dimensions;
// FIXME maybe use std::tuple here
void video_scaled_coords(int x, int y, int *xcoord, int *ycoord) {
float xscale = dimensions.rw / (vidinfo->aspect * vidinfo->h) / dimensions.dpiscale;
float yscale = dimensions.rh / vidinfo->h / dimensions.dpiscale;
float xo = dimensions.xo / dimensions.dpiscale;
float yo = dimensions.yo / dimensions.dpiscale;
*xcoord = (x - xo) / ((vidinfo->aspect * vidinfo->h * xscale)/(float)vidinfo->w);
*ycoord = ((y - yo) / yscale) + vidinfo->y;
}
static void nst_video_set_aspect() {
switch (setmgr->get_setting("v_aspect")->val) {
VideoManager::VideoManager(JGManager& jgm, SettingManager& setmgr)
: jgm(jgm), setmgr(setmgr) {
// Initialize video
vidinfo = jg_get_videoinfo();
videobuf = (uint32_t*)calloc(1, vidinfo->hmax * vidinfo->wmax * sizeof(uint32_t));
vidinfo->buf = (void*)&videobuf[0];
set_aspect();
int scale = setmgr.get_setting("v_scale")->val;
dimensions.ww = (aspect * vidinfo->h * scale) + 0.5;
dimensions.wh = (vidinfo->h * scale) + 0.5;
dimensions.rw = dimensions.ww;
dimensions.rh = dimensions.wh;
dimensions.dpiscale = 1.0;
}
VideoManager::~VideoManager() {
ogl_deinit();
if (videobuf) {
free(videobuf);
}
}
void VideoManager::set_aspect() {
switch (setmgr.get_setting("v_aspect")->val) {
case 0:
aspect = vidinfo->aspect;
break;
case 1:
if (jgm->get_setting("ntsc_filter")->val) {
if (jgm.get_setting("ntsc_filter")->val) {
aspect = 301/(double)vidinfo->h;
}
else {
@ -95,12 +92,12 @@ static void nst_video_set_aspect() {
}
}
void nst_video_rehash() {
GLuint filter = setmgr->get_setting("v_linearfilter")->val ? GL_LINEAR : GL_NEAREST;
void VideoManager::rehash() {
GLuint filter = setmgr.get_setting("v_linearfilter")->val ? GL_LINEAR : GL_NEAREST;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
nst_video_set_aspect();
set_aspect();
dimensions.rw = dimensions.ww;
dimensions.rh = dimensions.wh;
@ -115,8 +112,7 @@ void nst_video_rehash() {
dimensions.yo = (dimensions.wh - dimensions.rh) / 2;
}
void nst_ogl_init() {
// Initialize OpenGL
void VideoManager::ogl_init() {
// Generate texture for raw game output
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &gl_texture_id);
@ -128,7 +124,7 @@ void nst_ogl_init() {
vidinfo->wmax, vidinfo->hmax, 0, GL_BGRA, GL_UNSIGNED_BYTE,
videobuf);
GLuint filter = setmgr->get_setting("v_linearfilter")->val ? GL_LINEAR : GL_NEAREST;
GLuint filter = setmgr.get_setting("v_linearfilter")->val ? GL_LINEAR : GL_NEAREST;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@ -136,22 +132,22 @@ void nst_ogl_init() {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
}
void nst_ogl_deinit() {
void VideoManager::ogl_deinit() {
// Deinitialize OpenGL
if (gl_texture_id) {
glDeleteTextures(1, &gl_texture_id);
}
}
void nst_ogl_render() {
void VideoManager::ogl_render() {
// OSD Text
if (osdtext.drawtext) {
nst_video_text_draw(osdtext.textbuf, osdtext.xpos, osdtext.ypos, osdtext.bg);
text_draw(osdtext.textbuf, osdtext.xpos, osdtext.ypos, osdtext.bg);
osdtext.drawtext--;
}
if (osdtext.drawtime) {
nst_video_text_draw(osdtext.timebuf, 208, 218, false);
text_draw(osdtext.timebuf, 208, 218, false);
}
//jgrf_video_gl_refresh(); // Check for changes
@ -209,40 +205,28 @@ void nst_ogl_render() {
glEnd();
}
void nst_video_dimensions(int *w, int *h) {
void VideoManager::get_dimensions(int *w, int *h) {
*w = dimensions.rw;
*h = dimensions.rh;
}
void nst_video_resize(int w, int h) {
// FIXME maybe use std::tuple here
void VideoManager::get_scaled_coords(int x, int y, int *xcoord, int *ycoord) {
float xscale = dimensions.rw / (vidinfo->aspect * vidinfo->h) / dimensions.dpiscale;
float yscale = dimensions.rh / vidinfo->h / dimensions.dpiscale;
float xo = dimensions.xo / dimensions.dpiscale;
float yo = dimensions.yo / dimensions.dpiscale;
*xcoord = (x - xo) / ((vidinfo->aspect * vidinfo->h * xscale)/(float)vidinfo->w);
*ycoord = ((y - yo) / yscale) + vidinfo->y;
}
void VideoManager::resize(int w, int h) {
dimensions.ww = w;
dimensions.wh = h;
nst_video_rehash();
rehash();
}
void video_init(SettingManager *s, JGManager *j) {
setmgr = s;
jgm = j;
// Initialize video
vidinfo = jg_get_videoinfo();
vidinfo->buf = (void*)&videobuf[0];
nst_video_set_aspect();
int scale = setmgr->get_setting("v_scale")->val;
dimensions.ww = (aspect * vidinfo->h * scale) + 0.5;
dimensions.wh = (vidinfo->h * scale) + 0.5;
dimensions.rw = dimensions.ww;
dimensions.rh = dimensions.wh;
dimensions.dpiscale = 1.0;
/*if (nst_nsf()) {
video_clear_buffer();
video_disp_nsf();
}*/
}
void video_screenshot_flip(unsigned char *pixels, int width, int height, int bytes) {
/*void video_screenshot_flip(unsigned char *pixels, int width, int height, int bytes) {
// Flip the pixels
int rowsize = width * bytes;
unsigned char *row = (unsigned char*)malloc(rowsize);
@ -259,7 +243,7 @@ void video_screenshot_flip(unsigned char *pixels, int width, int height, int byt
void video_screenshot(const char* filename) {
// Take a screenshot in .png format
/*unsigned char *pixels;
unsigned char *pixels;
pixels = (unsigned char*)malloc(sizeof(unsigned char) * rendersize.w * rendersize.h * 4);
// Read the pixels and flip them vertically
@ -279,33 +263,10 @@ void video_screenshot(const char* filename) {
lodepng_encode32_file(filename, (const unsigned char*)pixels, rendersize.w, rendersize.h);
}
free(pixels);*/
}
free(pixels);
}*/
void video_clear_buffer() {
// Write black to the video buffer
//memset(videobuf, 0x00, vidinfo->wmax * Video::Output::HEIGHT * sizeof(uint32_t));
}
void video_disp_nsf() {
// Display NSF text
/*Nsf nsf(emulator);
int xscale = vidinfo->wmax / vidinfo->wmax;
int yscale = Video::Output::HEIGHT / Video::Output::HEIGHT;
nst_video_text_draw(nsf.GetName(), 4 * xscale, 16 * yscale, false);
nst_video_text_draw(nsf.GetArtist(), 4 * xscale, 28 * yscale, false);
nst_video_text_draw(nsf.GetCopyright(), 4 * xscale, 40 * yscale, false);
char currentsong[10];
snprintf(currentsong, sizeof(currentsong), "%d / %d", nsf.GetCurrentSong() +1, nsf.GetNumSongs());
nst_video_text_draw(currentsong, 4 * xscale, 52 * yscale, false);
nst_ogl_render();*/
}
void nst_video_print(const char *text, int xpos, int ypos, int seconds, bool bg) {
void VideoManager::text_print(const char *text, int xpos, int ypos, int seconds, bool bg) {
snprintf(osdtext.textbuf, sizeof(osdtext.textbuf), "%s", text);
osdtext.xpos = xpos;
osdtext.ypos = ypos;
@ -313,12 +274,12 @@ void nst_video_print(const char *text, int xpos, int ypos, int seconds, bool bg)
osdtext.bg = bg;
}
void nst_video_print_time(const char *timebuf, bool drawtime) {
void VideoManager::text_print_time(const char *timebuf, bool drawtime) {
snprintf(osdtext.timebuf, sizeof(osdtext.timebuf), "%s", timebuf);
osdtext.drawtime = drawtime;
}
void nst_video_text_draw(const char *text, int xpos, int ypos, bool bg) {
void VideoManager::text_draw(const char *text, int xpos, int ypos, bool bg) {
// Draw text on screen
uint32_t w = 0xc0c0c0c0; // "White", actually Grey
uint32_t b = 0x00000000; // Black
@ -342,8 +303,9 @@ void nst_video_text_draw(const char *text, int xpos, int ypos, bool bg) {
}
}
// FIXME this code is terrible
for (int tpos = 0; tpos < (8 * numchars); tpos+=8) {
nst_video_text_match(text, &letterxpos, &letterypos, letternum);
text_match(text, &letterxpos, &letterypos, letternum);
for (int row = 0; row < 8; row++) { // Draw Rows
for (int col = 0; col < 8; col++) { // Draw Columns
switch (nesfont[row + letterypos][col + letterxpos]) {
@ -365,7 +327,7 @@ void nst_video_text_draw(const char *text, int xpos, int ypos, bool bg) {
}
}
void nst_video_text_match(const char *text, int *xpos, int *ypos, int strpos) {
void VideoManager::text_match(const char *text, int *xpos, int *ypos, int strpos) {
// Match letters to draw on screen
switch (text[strpos]) {
case ' ': *xpos = 0; *ypos = 0; break;

View file

@ -0,0 +1,55 @@
#pragma once
#include "setmanager.h"
#include "jgmanager.h"
class VideoManager {
public:
VideoManager() = delete;
VideoManager(JGManager& jgm, SettingManager& setmgr);
~VideoManager();
void get_dimensions(int *w, int *h);
void get_scaled_coords(int x, int y, int *xcoord, int *ycoord);
void rehash();
void resize(int w, int h);
void set_aspect();
void ogl_init();
void ogl_deinit();
void ogl_render();
static void text_print(const char *text, int xpos, int ypos, int seconds, bool bg);
static void text_print_time(const char *timebuf, bool drawtime);
static void text_draw(const char *text, int xpos, int ypos, bool bg);
static void text_match(const char *text, int *xpos, int *ypos, int strpos);
private:
JGManager &jgm;
SettingManager &setmgr;
double aspect{1.0};
// Triangle and Texture vertices
float vertices[16]{
-1.0, -1.0, // Vertex 1 (X, Y) Left Bottom
-1.0, 1.0, // Vertex 2 (X, Y) Left Top
1.0, -1.0, // Vertex 3 (X, Y) Right Bottom
1.0, 1.0, // Vertex 4 (X, Y) Right Top
0.0, 0.0, // Texture 2 (X, Y) Left Top
0.0, 1.0, // Texture 1 (X, Y) Left Bottom
1.0, 0.0, // Texture 4 (X, Y) Right Top
1.0, 1.0, // Texture 3 (X, Y) Right Bottom
};
unsigned int gl_texture_id{0};
// Dimensions
struct _dimensions {
int ww; int wh;
float rw; float rh;
float xo; float yo;
float dpiscale;
} dimensions;
};