2013-09-01 16:27:21 +02:00
|
|
|
/*
|
2021-02-25 19:28:44 -05:00
|
|
|
** graphics.cpp
|
|
|
|
**
|
|
|
|
** This file is part of mkxp.
|
|
|
|
**
|
2023-10-04 21:07:34 +02:00
|
|
|
** Copyright (C) 2013 - 2021 Amaryllis Kulla <ancurio@mapleshrine.eu>
|
2021-02-25 19:28:44 -05:00
|
|
|
**
|
|
|
|
** mkxp 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.
|
|
|
|
**
|
|
|
|
** mkxp 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 mkxp. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2013-09-01 16:27:21 +02:00
|
|
|
|
|
|
|
#include "graphics.h"
|
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
#include "alstream.h"
|
2022-01-02 18:05:17 -05:00
|
|
|
#include "audio.h"
|
2020-02-26 13:28:51 -05:00
|
|
|
#include "binding.h"
|
|
|
|
#include "bitmap.h"
|
2014-08-12 22:21:57 +02:00
|
|
|
#include "config.h"
|
2020-02-26 13:28:51 -05:00
|
|
|
#include "debugwriter.h"
|
|
|
|
#include "disposable.h"
|
2023-10-26 18:35:39 +00:00
|
|
|
#include "etc.h"
|
2020-02-26 13:28:51 -05:00
|
|
|
#include "etc-internal.h"
|
2013-09-01 16:27:21 +02:00
|
|
|
#include "eventthread.h"
|
2019-08-19 08:59:35 -04:00
|
|
|
#include "filesystem.h"
|
2020-02-26 13:28:51 -05:00
|
|
|
#include "gl-fun.h"
|
|
|
|
#include "gl-util.h"
|
|
|
|
#include "glstate.h"
|
2014-08-24 07:36:19 +02:00
|
|
|
#include "intrulist.h"
|
2020-02-26 13:28:51 -05:00
|
|
|
#include "quad.h"
|
|
|
|
#include "scene.h"
|
|
|
|
#include "shader.h"
|
|
|
|
#include "sharedstate.h"
|
|
|
|
#include "texpool.h"
|
2022-01-16 16:21:55 -05:00
|
|
|
#include "theoraplay/theoraplay.h"
|
2020-02-26 13:28:51 -05:00
|
|
|
#include "util.h"
|
2021-02-25 19:28:44 -05:00
|
|
|
#include "input.h"
|
2022-01-21 00:38:13 -05:00
|
|
|
#include "sprite.h"
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2022-01-06 19:32:13 -05:00
|
|
|
#include <SDL.h>
|
2013-12-20 09:17:15 +01:00
|
|
|
#include <SDL_image.h>
|
2020-02-26 13:28:51 -05:00
|
|
|
#include <SDL_timer.h>
|
|
|
|
#include <SDL_video.h>
|
2021-03-01 01:51:15 -05:00
|
|
|
#include <SDL_mutex.h>
|
2021-06-04 14:29:45 -04:00
|
|
|
#include <SDL_thread.h>
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2020-12-31 14:50:07 -05:00
|
|
|
#ifdef MKXPZ_STEAM
|
2020-03-02 03:52:42 -05:00
|
|
|
#include "steamshim_child.h"
|
2020-02-28 03:23:16 -05:00
|
|
|
#endif
|
|
|
|
|
2013-12-08 13:19:22 +01:00
|
|
|
#include <algorithm>
|
2020-02-26 13:28:51 -05:00
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/time.h>
|
2020-12-26 17:46:01 -05:00
|
|
|
#include <unistd.h>
|
2020-02-26 13:28:51 -05:00
|
|
|
#include <time.h>
|
2022-01-23 06:20:13 -05:00
|
|
|
#include <cmath>
|
2023-04-25 22:48:24 -04:00
|
|
|
#include <climits>
|
2013-10-06 14:11:50 +02:00
|
|
|
|
2021-02-27 08:17:14 -05:00
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
#define DEF_SCREEN_W (rgssVer == 1 ? 640 : 544)
|
|
|
|
#define DEF_SCREEN_H (rgssVer == 1 ? 480 : 416)
|
2020-11-22 03:48:03 -05:00
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
#define DEF_FRAMERATE (rgssVer == 1 ? 40 : 60)
|
|
|
|
|
2022-01-02 18:05:17 -05:00
|
|
|
#define DEF_MAX_VIDEO_FRAMES 30
|
2022-01-06 19:32:13 -05:00
|
|
|
#define VIDEO_DELAY 10
|
2023-02-08 21:53:10 -05:00
|
|
|
#define MOVIE_AUDIO_BUFFER_SIZE 2048
|
|
|
|
#define AUDIO_BUFFER_LEN_MS 2000
|
2022-01-06 19:32:13 -05:00
|
|
|
|
|
|
|
typedef struct AudioQueue
|
|
|
|
{
|
|
|
|
const THEORAPLAY_AudioPacket *audio;
|
|
|
|
int offset;
|
|
|
|
struct AudioQueue *next;
|
|
|
|
} AudioQueue;
|
2022-01-02 18:05:17 -05:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
|
|
|
|
static long readMovie(THEORAPLAY_Io *io, void *buf, long buflen)
|
|
|
|
{
|
|
|
|
SDL_RWops *f = (SDL_RWops *) io->userdata;
|
|
|
|
return (long) SDL_RWread(f, buf, 1, buflen);
|
|
|
|
} // IoFopenRead
|
|
|
|
|
|
|
|
|
|
|
|
static void closeMovie(THEORAPLAY_Io *io)
|
|
|
|
{
|
|
|
|
SDL_RWops *f = (SDL_RWops *) io->userdata;
|
|
|
|
SDL_RWclose(f);
|
|
|
|
free(io);
|
|
|
|
} // IoFopenClose
|
|
|
|
|
|
|
|
|
|
|
|
struct Movie
|
|
|
|
{
|
|
|
|
THEORAPLAY_Decoder *decoder;
|
|
|
|
const THEORAPLAY_AudioPacket *audio;
|
|
|
|
const THEORAPLAY_VideoFrame *video;
|
|
|
|
bool hasVideo;
|
|
|
|
bool hasAudio;
|
2022-01-23 01:46:13 -05:00
|
|
|
bool skippable;
|
2022-01-13 22:20:43 -05:00
|
|
|
Bitmap *videoBitmap;
|
2022-07-03 06:59:46 -04:00
|
|
|
SDL_RWops srcOps;
|
2023-02-08 21:53:10 -05:00
|
|
|
SDL_Thread *audioThread;
|
|
|
|
AtomicFlag audioThreadTermReq;
|
|
|
|
volatile AudioQueue *audioQueueHead;
|
|
|
|
volatile AudioQueue *audioQueueTail;
|
|
|
|
ALuint audioSource;
|
|
|
|
ALuint alBuffers[STREAM_BUFS];
|
|
|
|
ALshort audioBuffer[MOVIE_AUDIO_BUFFER_SIZE];
|
|
|
|
SDL_mutex *audioMutex;
|
|
|
|
|
|
|
|
Movie(bool skippable_)
|
|
|
|
: decoder(0), audio(0), video(0), skippable(skippable_), videoBitmap(0), audioThread(0)
|
2022-01-16 16:21:55 -05:00
|
|
|
{
|
|
|
|
}
|
2022-01-13 22:20:43 -05:00
|
|
|
bool preparePlayback()
|
|
|
|
{
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
// https://theora.org/doc/libtheora-1.0/codec_8h.html
|
|
|
|
// https://ffmpeg.org/doxygen/0.11/group__lavc__misc__pixfmt.html
|
2022-07-03 06:59:46 -04:00
|
|
|
THEORAPLAY_Io *io = (THEORAPLAY_Io *) malloc(sizeof (THEORAPLAY_Io));
|
2022-01-13 22:20:43 -05:00
|
|
|
if(!io) {
|
|
|
|
SDL_RWclose(&srcOps);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
io->read = readMovie;
|
|
|
|
io->close = closeMovie;
|
|
|
|
io->userdata = &srcOps;
|
|
|
|
decoder = THEORAPLAY_startDecode(io, DEF_MAX_VIDEO_FRAMES, THEORAPLAY_VIDFMT_RGBA);
|
|
|
|
if (!decoder) {
|
|
|
|
SDL_RWclose(&srcOps);
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
// Wait until the decoder has parsed out some basic truths from the file.
|
|
|
|
while (!THEORAPLAY_isInitialized(decoder)) {
|
|
|
|
SDL_Delay(VIDEO_DELAY);
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
// Once we're initialized, we can tell if this file has audio and/or video.
|
|
|
|
hasAudio = THEORAPLAY_hasAudioStream(decoder);
|
|
|
|
hasVideo = THEORAPLAY_hasVideoStream(decoder);
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
// Queue up the audio
|
|
|
|
if (hasAudio) {
|
|
|
|
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL) {
|
|
|
|
if ((THEORAPLAY_availableVideo(decoder) >= DEF_MAX_VIDEO_FRAMES)) {
|
|
|
|
break; // we'll never progress, there's no audio yet but we've prebuffered as much as we plan to.
|
|
|
|
}
|
|
|
|
SDL_Delay(VIDEO_DELAY);
|
|
|
|
}
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
// No video, so no point in doing anything else
|
|
|
|
if (!hasVideo) {
|
|
|
|
THEORAPLAY_stopDecode(decoder);
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
// Wait until we have video
|
|
|
|
while ((video = THEORAPLAY_getVideo(decoder)) == NULL) {
|
|
|
|
SDL_Delay(VIDEO_DELAY);
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
// Wait until we have audio, if applicable
|
|
|
|
audio = NULL;
|
|
|
|
if (hasAudio) {
|
|
|
|
while ((audio = THEORAPLAY_getAudio(decoder)) == NULL && THEORAPLAY_availableVideo(decoder) < DEF_MAX_VIDEO_FRAMES) {
|
|
|
|
SDL_Delay(VIDEO_DELAY);
|
|
|
|
}
|
|
|
|
}
|
2022-01-23 01:46:13 -05:00
|
|
|
videoBitmap = new Bitmap(video->width, video->height);
|
2023-02-08 21:53:10 -05:00
|
|
|
audioQueueHead = NULL;
|
|
|
|
audioQueueTail = NULL;
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
return true;
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
void queueAudioPacket(const THEORAPLAY_AudioPacket *audio) {
|
2022-01-13 22:20:43 -05:00
|
|
|
AudioQueue *item = NULL;
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
if (!audio) {
|
|
|
|
return;
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
item = (AudioQueue *) malloc(sizeof (AudioQueue));
|
|
|
|
if (!item) {
|
|
|
|
THEORAPLAY_freeAudio(audio);
|
|
|
|
return; // oh well.
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
item->audio = audio;
|
|
|
|
item->offset = 0;
|
|
|
|
item->next = NULL;
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
SDL_LockMutex(audioMutex);
|
|
|
|
if (audioQueueTail) {
|
|
|
|
audioQueueTail->next = item;
|
2022-01-13 22:20:43 -05:00
|
|
|
} else {
|
2023-02-08 21:53:10 -05:00
|
|
|
audioQueueHead = item;
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
2023-02-08 21:53:10 -05:00
|
|
|
audioQueueTail = item;
|
|
|
|
SDL_UnlockMutex(audioMutex);
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
void bufferMovieAudio(THEORAPLAY_Decoder *decoder, const Uint32 now) {
|
2022-01-13 22:20:43 -05:00
|
|
|
const THEORAPLAY_AudioPacket *audio;
|
|
|
|
while ((audio = THEORAPLAY_getAudio(decoder)) != NULL) {
|
|
|
|
queueAudioPacket(audio);
|
2023-02-08 21:53:10 -05:00
|
|
|
if (audio->playms >= now + AUDIO_BUFFER_LEN_MS) { // don't let this get too far ahead.
|
2022-01-13 22:20:43 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-08 21:53:10 -05:00
|
|
|
|
|
|
|
void streamMovieAudio(){
|
|
|
|
ALint state = 0;
|
|
|
|
ALint procBufs = STREAM_BUFS;
|
|
|
|
volatile AudioQueue *audioPacketAndOffset;
|
|
|
|
int channels;
|
|
|
|
int sampleRate;
|
|
|
|
float *sourceSamples;
|
|
|
|
ALuint samplesToProcess;
|
|
|
|
ALshort *sampleBuffer;
|
|
|
|
ALuint remainingSamples;
|
|
|
|
|
|
|
|
while(true) {
|
|
|
|
while(procBufs--) {
|
|
|
|
// Quit if audio thread terminate request has been made
|
|
|
|
if (audioThreadTermReq) return;
|
|
|
|
|
|
|
|
remainingSamples = MOVIE_AUDIO_BUFFER_SIZE;
|
|
|
|
sampleBuffer = audioBuffer;
|
|
|
|
SDL_LockMutex(audioMutex);
|
|
|
|
|
|
|
|
while(audioQueueHead && (remainingSamples > 0)) {
|
|
|
|
audioPacketAndOffset = audioQueueHead;
|
|
|
|
channels = audioPacketAndOffset->audio->channels;
|
|
|
|
sampleRate = audioPacketAndOffset->audio->freq;
|
|
|
|
sourceSamples = audioPacketAndOffset->audio->samples + (audioPacketAndOffset->offset * channels);
|
|
|
|
samplesToProcess = (audioPacketAndOffset->audio->frames - audioPacketAndOffset->offset) * channels;
|
|
|
|
|
|
|
|
if (samplesToProcess > remainingSamples) samplesToProcess = remainingSamples;
|
|
|
|
|
|
|
|
for (ALuint i = 0; i < samplesToProcess; i++) {
|
|
|
|
const float val = (*(sourceSamples++));
|
|
|
|
if (val < -1.0f) {
|
|
|
|
*(sampleBuffer++) = SHRT_MIN;
|
|
|
|
} else if (val > 1.0f) {
|
|
|
|
*(sampleBuffer++) = SHRT_MAX;
|
|
|
|
} else {
|
|
|
|
*(sampleBuffer++) = (ALshort) (val * SHRT_MAX);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Necessary to remember position between repeated iterations
|
|
|
|
audioPacketAndOffset->offset += (samplesToProcess / channels);
|
|
|
|
remainingSamples -= samplesToProcess;
|
|
|
|
|
|
|
|
// The current audio packet has been completed
|
|
|
|
if ((audioPacketAndOffset->offset) >= audioPacketAndOffset->audio->frames) {
|
|
|
|
audioQueueHead = audioPacketAndOffset->next;
|
|
|
|
THEORAPLAY_freeAudio(audioPacketAndOffset->audio);
|
|
|
|
free((void *) audioPacketAndOffset);
|
|
|
|
}
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
2023-02-08 21:53:10 -05:00
|
|
|
|
|
|
|
if(!audioQueueHead) audioQueueTail = NULL;
|
|
|
|
|
|
|
|
SDL_UnlockMutex(audioMutex);
|
|
|
|
|
|
|
|
alBufferData(alBuffers[procBufs], channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, audioBuffer,
|
|
|
|
(MOVIE_AUDIO_BUFFER_SIZE - remainingSamples) * sizeof(ALshort), sampleRate);
|
|
|
|
alSourceQueueBuffers(audioSource, 1, &alBuffers[procBufs]);
|
|
|
|
alGetSourcei(audioSource, AL_SOURCE_STATE, &state);
|
|
|
|
if(state != AL_PLAYING) alSourcePlay(audioSource);
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
2023-02-08 21:53:10 -05:00
|
|
|
|
|
|
|
// Periodically check the buffers until one is available
|
|
|
|
while(true) {
|
|
|
|
alGetSourcei(audioSource, AL_BUFFERS_PROCESSED, &procBufs);
|
|
|
|
if(procBufs > 0) break;
|
|
|
|
SDL_Delay(AUDIO_SLEEP);
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
2023-02-08 21:53:10 -05:00
|
|
|
alSourceUnqueueBuffers(audioSource, procBufs, alBuffers);
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
bool startAudio(float volume)
|
2022-01-13 22:20:43 -05:00
|
|
|
{
|
2023-02-08 21:53:10 -05:00
|
|
|
alGenSources(1, &audioSource);
|
|
|
|
alGenBuffers(STREAM_BUFS, alBuffers);
|
|
|
|
alSourcef(audioSource, AL_GAIN, volume);
|
|
|
|
|
|
|
|
audioThreadTermReq.clear();
|
|
|
|
audioMutex = SDL_CreateMutex();
|
2022-01-13 22:20:43 -05:00
|
|
|
queueAudioPacket(audio);
|
|
|
|
audio = NULL;
|
2023-02-08 21:53:10 -05:00
|
|
|
bufferMovieAudio(decoder, 0);
|
2023-04-25 22:48:24 -04:00
|
|
|
audioThread = createSDLThread <Movie, &Movie::streamMovieAudio>(this, "movieaudio");
|
2023-02-08 21:53:10 -05:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
return true;
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
void play(float volume)
|
2022-01-13 22:20:43 -05:00
|
|
|
{
|
2023-02-08 21:53:10 -05:00
|
|
|
Uint32 frameMs = 0;
|
2022-01-13 22:20:43 -05:00
|
|
|
Uint32 baseTicks = SDL_GetTicks();
|
|
|
|
bool openedAudio = false;
|
|
|
|
while (THEORAPLAY_isDecoding(decoder)) {
|
|
|
|
// Check for reset/shutdown input
|
|
|
|
if(shState->graphics().updateMovieInput(this)) break;
|
2022-01-23 01:46:13 -05:00
|
|
|
|
|
|
|
// Check for attempted skip
|
|
|
|
if (skippable) {
|
|
|
|
shState->input().update();
|
|
|
|
if (shState->input().isTriggered(Input::C) || shState->input().isTriggered(Input::B)) break;
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
const Uint32 now = SDL_GetTicks() - baseTicks;
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
if (!video) {
|
|
|
|
video = THEORAPLAY_getVideo(decoder);
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
if (hasAudio) {
|
|
|
|
if (!audio) {
|
|
|
|
audio = THEORAPLAY_getAudio(decoder);
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
if (audio && !openedAudio) {
|
2023-02-08 21:53:10 -05:00
|
|
|
if(!startAudio(volume)){
|
2022-01-13 22:20:43 -05:00
|
|
|
Debug() << "Error opening movie audio!";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
openedAudio = true;
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
if (video && (video->playms <= now)) {
|
|
|
|
frameMs = (video->fps == 0.0) ? 0 : ((Uint32) (1000.0 / video->fps));
|
|
|
|
if ( frameMs && ((now - video->playms) >= frameMs) )
|
|
|
|
{
|
|
|
|
// Skip frames to catch up
|
|
|
|
const THEORAPLAY_VideoFrame *last = video;
|
|
|
|
while ((video = THEORAPLAY_getVideo(decoder)) != NULL)
|
|
|
|
{
|
|
|
|
THEORAPLAY_freeVideo(last);
|
|
|
|
last = video;
|
|
|
|
if ((now - video->playms) < frameMs)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!video)
|
|
|
|
video = last;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Application is too far behind
|
|
|
|
if (!video) {
|
|
|
|
Debug() << "WARNING: Video playback cannot keep up!";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Got a video frame, now draw it
|
2022-01-23 04:03:50 -05:00
|
|
|
videoBitmap->replaceRaw(video->pixels, video->width * video->height * 4);
|
2023-02-08 21:53:10 -05:00
|
|
|
shState->graphics().update(false);
|
2022-01-23 04:03:50 -05:00
|
|
|
THEORAPLAY_freeVideo(video);
|
|
|
|
video = NULL;
|
2023-02-08 21:53:10 -05:00
|
|
|
|
|
|
|
} else {
|
|
|
|
// Next video frame not yet ready, let the CPU breathe
|
|
|
|
SDL_Delay(VIDEO_DELAY);
|
2022-01-23 04:03:50 -05:00
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
if (openedAudio) {
|
2023-02-08 21:53:10 -05:00
|
|
|
bufferMovieAudio(decoder, now);
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
~Movie()
|
|
|
|
{
|
|
|
|
if (hasAudio) {
|
2023-02-08 21:53:10 -05:00
|
|
|
if (audioQueueTail) {
|
|
|
|
THEORAPLAY_freeAudio(audioQueueTail->audio);
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
2023-02-08 21:53:10 -05:00
|
|
|
audioQueueTail = NULL;
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
if (audioQueueHead) {
|
|
|
|
THEORAPLAY_freeAudio(audioQueueHead->audio);
|
2022-01-13 22:20:43 -05:00
|
|
|
}
|
2023-02-08 21:53:10 -05:00
|
|
|
audioQueueHead = NULL;
|
2023-10-08 22:33:12 -04:00
|
|
|
SDL_DestroyMutex(audioMutex);
|
|
|
|
audioThreadTermReq.set();
|
2023-10-10 21:27:15 -04:00
|
|
|
if(audioThread) {
|
|
|
|
SDL_WaitThread(audioThread, 0);
|
|
|
|
audioThread = 0;
|
|
|
|
}
|
2023-10-08 22:33:12 -04:00
|
|
|
alSourceStop(audioSource);
|
|
|
|
alDeleteSources(1, &audioSource);
|
|
|
|
alDeleteBuffers(STREAM_BUFS, alBuffers);
|
2023-02-08 21:53:10 -05:00
|
|
|
}
|
2022-01-13 22:20:43 -05:00
|
|
|
if (video) THEORAPLAY_freeVideo(video);
|
|
|
|
if (audio) THEORAPLAY_freeAudio(audio);
|
|
|
|
if (decoder) THEORAPLAY_stopDecode(decoder);
|
|
|
|
delete videoBitmap;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct MovieOpenHandler : FileSystem::OpenHandler
|
|
|
|
{
|
|
|
|
SDL_RWops *srcOps;
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
MovieOpenHandler(SDL_RWops &srcOps)
|
|
|
|
: srcOps(&srcOps)
|
|
|
|
{}
|
|
|
|
|
|
|
|
bool tryRead(SDL_RWops &ops, const char *ext)
|
|
|
|
{
|
|
|
|
*srcOps = ops;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
struct PingPong {
|
2021-02-25 19:28:44 -05:00
|
|
|
TEXFBO rt[2];
|
|
|
|
uint8_t srcInd, dstInd;
|
|
|
|
int screenW, screenH;
|
|
|
|
|
|
|
|
PingPong(int screenW, int screenH)
|
|
|
|
: srcInd(0), dstInd(1), screenW(screenW), screenH(screenH) {
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
TEXFBO::init(rt[i]);
|
|
|
|
TEXFBO::allocEmpty(rt[i], screenW, screenH);
|
|
|
|
TEXFBO::linkFBO(rt[i]);
|
|
|
|
gl.ClearColor(0, 0, 0, 1);
|
|
|
|
FBO::clear();
|
|
|
|
}
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
~PingPong() {
|
|
|
|
for (int i = 0; i < 2; ++i)
|
2022-07-03 06:59:46 -04:00
|
|
|
TEXFBO::fini(rt[i]);
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
TEXFBO &backBuffer() { return rt[srcInd]; }
|
|
|
|
|
|
|
|
TEXFBO &frontBuffer() { return rt[dstInd]; }
|
|
|
|
|
|
|
|
/* Better not call this during render cycles */
|
|
|
|
void resize(int width, int height) {
|
|
|
|
screenW = width;
|
|
|
|
screenH = height;
|
|
|
|
|
|
|
|
for (int i = 0; i < 2; ++i)
|
2022-07-03 06:59:46 -04:00
|
|
|
TEXFBO::allocEmpty(rt[i], width, height);
|
2021-02-25 19:28:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void startRender() { bind(); }
|
|
|
|
|
|
|
|
void swapRender() {
|
|
|
|
std::swap(srcInd, dstInd);
|
|
|
|
|
|
|
|
bind();
|
|
|
|
}
|
|
|
|
|
|
|
|
void clearBuffers() {
|
|
|
|
glState.clearColor.pushSet(Vec4(0, 0, 0, 1));
|
|
|
|
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
FBO::bind(rt[i].fbo);
|
|
|
|
FBO::clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
glState.clearColor.pop();
|
|
|
|
}
|
|
|
|
|
2013-09-01 16:27:21 +02:00
|
|
|
private:
|
2021-02-25 19:28:44 -05:00
|
|
|
void bind() { FBO::bind(rt[dstInd].fbo); }
|
2013-09-01 16:27:21 +02:00
|
|
|
};
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
class ScreenScene : public Scene {
|
2013-09-01 16:27:21 +02:00
|
|
|
public:
|
2021-02-25 19:28:44 -05:00
|
|
|
ScreenScene(int width, int height) : pp(width, height) {
|
|
|
|
updateReso(width, height);
|
|
|
|
|
|
|
|
brightEffect = false;
|
|
|
|
brightnessQuad.setColor(Vec4());
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
void composite() {
|
|
|
|
const int w = geometry.rect.w;
|
|
|
|
const int h = geometry.rect.h;
|
|
|
|
|
|
|
|
shState->prepareDraw();
|
|
|
|
|
|
|
|
pp.startRender();
|
|
|
|
|
|
|
|
glState.viewport.set(IntRect(0, 0, w, h));
|
|
|
|
|
|
|
|
FBO::clear();
|
|
|
|
|
|
|
|
Scene::composite();
|
|
|
|
|
|
|
|
if (brightEffect) {
|
|
|
|
SimpleColorShader &shader = shState->shaders().simpleColor;
|
|
|
|
shader.bind();
|
|
|
|
shader.applyViewportProj();
|
|
|
|
shader.setTranslation(Vec2i());
|
|
|
|
|
|
|
|
brightnessQuad.draw();
|
|
|
|
}
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
void requestViewportRender(const Vec4 &c, const Vec4 &f, const Vec4 &t) {
|
|
|
|
const IntRect &viewpRect = glState.scissorBox.get();
|
|
|
|
const IntRect &screenRect = geometry.rect;
|
|
|
|
|
|
|
|
const bool toneRGBEffect = t.xyzNotNull();
|
|
|
|
const bool toneGrayEffect = t.w != 0;
|
|
|
|
const bool colorEffect = c.w > 0;
|
|
|
|
const bool flashEffect = f.w > 0;
|
|
|
|
|
|
|
|
if (toneGrayEffect) {
|
|
|
|
pp.swapRender();
|
|
|
|
|
|
|
|
if (!viewpRect.encloses(screenRect)) {
|
|
|
|
/* Scissor test _does_ affect FBO blit operations,
|
|
|
|
* and since we're inside the draw cycle, it will
|
|
|
|
* be turned on, so turn it off temporarily */
|
|
|
|
glState.scissorTest.pushSet(false);
|
|
|
|
|
|
|
|
GLMeta::blitBegin(pp.frontBuffer());
|
|
|
|
GLMeta::blitSource(pp.backBuffer());
|
|
|
|
GLMeta::blitRectangle(geometry.rect, Vec2i());
|
|
|
|
GLMeta::blitEnd();
|
|
|
|
|
|
|
|
glState.scissorTest.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
GrayShader &shader = shState->shaders().gray;
|
|
|
|
shader.bind();
|
|
|
|
shader.setGray(t.w);
|
|
|
|
shader.applyViewportProj();
|
|
|
|
shader.setTexSize(screenRect.size());
|
|
|
|
|
|
|
|
TEX::bind(pp.backBuffer().tex);
|
|
|
|
|
|
|
|
glState.blend.pushSet(false);
|
|
|
|
screenQuad.draw();
|
|
|
|
glState.blend.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!toneRGBEffect && !colorEffect && !flashEffect)
|
|
|
|
return;
|
|
|
|
|
|
|
|
FlatColorShader &shader = shState->shaders().flatColor;
|
|
|
|
shader.bind();
|
|
|
|
shader.applyViewportProj();
|
|
|
|
|
|
|
|
if (toneRGBEffect) {
|
|
|
|
/* First split up additive / substractive components */
|
|
|
|
Vec4 add, sub;
|
|
|
|
|
|
|
|
if (t.x > 0)
|
|
|
|
add.x = t.x;
|
|
|
|
if (t.y > 0)
|
|
|
|
add.y = t.y;
|
|
|
|
if (t.z > 0)
|
|
|
|
add.z = t.z;
|
|
|
|
|
|
|
|
if (t.x < 0)
|
|
|
|
sub.x = -t.x;
|
|
|
|
if (t.y < 0)
|
|
|
|
sub.y = -t.y;
|
|
|
|
if (t.z < 0)
|
|
|
|
sub.z = -t.z;
|
|
|
|
|
|
|
|
/* Then apply them using hardware blending */
|
|
|
|
gl.BlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_ONE);
|
|
|
|
|
|
|
|
if (add.xyzNotNull()) {
|
|
|
|
gl.BlendEquation(GL_FUNC_ADD);
|
|
|
|
shader.setColor(add);
|
|
|
|
|
|
|
|
screenQuad.draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sub.xyzNotNull()) {
|
|
|
|
gl.BlendEquation(GL_FUNC_REVERSE_SUBTRACT);
|
|
|
|
shader.setColor(sub);
|
|
|
|
|
|
|
|
screenQuad.draw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (colorEffect || flashEffect) {
|
|
|
|
gl.BlendEquation(GL_FUNC_ADD);
|
|
|
|
gl.BlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO,
|
|
|
|
GL_ONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (colorEffect) {
|
|
|
|
shader.setColor(c);
|
|
|
|
screenQuad.draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flashEffect) {
|
|
|
|
shader.setColor(f);
|
|
|
|
screenQuad.draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
glState.blendMode.refresh();
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
void setBrightness(float norm) {
|
|
|
|
brightnessQuad.setColor(Vec4(0, 0, 0, 1.0f - norm));
|
|
|
|
|
|
|
|
brightEffect = norm < 1.0f;
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
void updateReso(int width, int height) {
|
|
|
|
geometry.rect.w = width;
|
|
|
|
geometry.rect.h = height;
|
|
|
|
|
|
|
|
screenQuad.setTexPosRect(geometry.rect, geometry.rect);
|
|
|
|
brightnessQuad.setTexPosRect(geometry.rect, geometry.rect);
|
|
|
|
|
|
|
|
notifyGeometryChange();
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
void setResolution(int width, int height) {
|
|
|
|
pp.resize(width, height);
|
|
|
|
updateReso(width, height);
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
PingPong &getPP() { return pp; }
|
|
|
|
|
2013-09-01 16:27:21 +02:00
|
|
|
private:
|
2021-02-25 19:28:44 -05:00
|
|
|
PingPong pp;
|
|
|
|
Quad screenQuad;
|
|
|
|
|
|
|
|
Quad brightnessQuad;
|
|
|
|
bool brightEffect;
|
2013-09-01 16:27:21 +02:00
|
|
|
};
|
|
|
|
|
2014-06-11 03:09:29 +02:00
|
|
|
/* Nanoseconds per second */
|
|
|
|
#define NS_PER_S 1000000000
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
struct FPSLimiter {
|
2021-02-25 19:28:44 -05:00
|
|
|
uint64_t lastTickCount;
|
|
|
|
|
|
|
|
/* ticks per frame */
|
|
|
|
int64_t tpf;
|
|
|
|
|
|
|
|
/* Ticks per second */
|
|
|
|
const uint64_t tickFreq;
|
|
|
|
|
|
|
|
/* Ticks per milisecond */
|
|
|
|
const uint64_t tickFreqMS;
|
|
|
|
|
|
|
|
/* Ticks per nanosecond */
|
|
|
|
const double tickFreqNS;
|
|
|
|
|
|
|
|
bool disabled;
|
|
|
|
|
|
|
|
/* Data for frame timing adjustment */
|
|
|
|
struct {
|
|
|
|
/* Last tick count */
|
|
|
|
uint64_t last;
|
|
|
|
|
|
|
|
/* How far behind/in front we are for ideal frame timing */
|
|
|
|
int64_t idealDiff;
|
|
|
|
|
|
|
|
bool resetFlag;
|
|
|
|
} adj;
|
|
|
|
|
|
|
|
FPSLimiter(uint16_t desiredFPS)
|
|
|
|
: lastTickCount(SDL_GetPerformanceCounter()),
|
|
|
|
tickFreq(SDL_GetPerformanceFrequency()), tickFreqMS(tickFreq / 1000),
|
|
|
|
tickFreqNS((double)tickFreq / NS_PER_S), disabled(false) {
|
|
|
|
setDesiredFPS(desiredFPS);
|
|
|
|
|
|
|
|
adj.last = SDL_GetPerformanceCounter();
|
|
|
|
adj.idealDiff = 0;
|
|
|
|
adj.resetFlag = false;
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
void setDesiredFPS(uint16_t value) { tpf = tickFreq / value; }
|
|
|
|
|
|
|
|
void delay() {
|
|
|
|
if (disabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int64_t tickDelta = SDL_GetPerformanceCounter() - lastTickCount;
|
|
|
|
int64_t toDelay = tpf - tickDelta;
|
|
|
|
|
|
|
|
/* Compensate for the last delta
|
|
|
|
* to the ideal timestep */
|
|
|
|
toDelay -= adj.idealDiff;
|
|
|
|
|
|
|
|
if (toDelay < 0)
|
|
|
|
toDelay = 0;
|
|
|
|
|
|
|
|
delayTicks(toDelay);
|
|
|
|
|
|
|
|
uint64_t now = lastTickCount = SDL_GetPerformanceCounter();
|
|
|
|
int64_t diff = now - adj.last;
|
|
|
|
adj.last = now;
|
|
|
|
|
|
|
|
/* Recalculate our temporal position
|
|
|
|
* relative to the ideal timestep */
|
|
|
|
adj.idealDiff = diff - tpf + adj.idealDiff;
|
|
|
|
|
|
|
|
if (adj.resetFlag) {
|
|
|
|
adj.idealDiff = 0;
|
|
|
|
adj.resetFlag = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void resetFrameAdjust() { adj.resetFlag = true; }
|
|
|
|
|
|
|
|
/* If we're more than a full frame's worth
|
|
|
|
* of ticks behind the ideal timestep,
|
|
|
|
* there's no choice but to skip frame(s)
|
|
|
|
* to catch up */
|
|
|
|
bool frameSkipRequired() const {
|
|
|
|
if (disabled)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return adj.idealDiff > tpf;
|
|
|
|
}
|
|
|
|
|
2013-10-06 14:11:50 +02:00
|
|
|
private:
|
2021-02-25 19:28:44 -05:00
|
|
|
void delayTicks(uint64_t ticks) {
|
2014-06-12 12:54:10 +02:00
|
|
|
#if defined(HAVE_NANOSLEEP)
|
2021-02-25 19:28:44 -05:00
|
|
|
struct timespec req;
|
|
|
|
uint64_t nsec = ticks / tickFreqNS;
|
|
|
|
req.tv_sec = nsec / NS_PER_S;
|
|
|
|
req.tv_nsec = nsec % NS_PER_S;
|
|
|
|
errno = 0;
|
|
|
|
|
|
|
|
while (nanosleep(&req, &req) == -1) {
|
|
|
|
int err = errno;
|
|
|
|
errno = 0;
|
|
|
|
|
|
|
|
if (err == EINTR)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Debug() << "nanosleep failed. errno:" << err;
|
|
|
|
SDL_Delay(ticks / tickFreqMS);
|
|
|
|
break;
|
|
|
|
}
|
2013-10-06 14:11:50 +02:00
|
|
|
#else
|
2021-02-25 19:28:44 -05:00
|
|
|
SDL_Delay(ticks / tickFreqMS);
|
2013-10-06 14:11:50 +02:00
|
|
|
#endif
|
2021-02-25 19:28:44 -05:00
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
};
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
struct GraphicsPrivate {
|
2021-02-25 19:28:44 -05:00
|
|
|
/* Screen resolution, ie. the resolution at which
|
|
|
|
* RGSS renders at (settable with Graphics.resize_screen).
|
|
|
|
* Can only be changed from within RGSS */
|
|
|
|
Vec2i scRes;
|
2023-05-28 22:03:28 +00:00
|
|
|
Vec2i scResLores;
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
/* Screen size, to which the rendered frames are scaled up.
|
|
|
|
* This can be smaller than the window size when fixed aspect
|
|
|
|
* ratio is enforced */
|
|
|
|
Vec2i scSize;
|
|
|
|
|
|
|
|
/* Actual physical size of the game window */
|
|
|
|
Vec2i winSize;
|
|
|
|
|
|
|
|
/* Offset in the game window at which the scaled game screen
|
|
|
|
* is blitted inside the game window */
|
|
|
|
Vec2i scOffset;
|
|
|
|
|
2021-05-21 16:28:51 -04:00
|
|
|
// Scaling factor, used to display the screen properly
|
|
|
|
// on Retina displays
|
|
|
|
int scalingFactor;
|
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
ScreenScene screen;
|
|
|
|
RGSSThreadData *threadData;
|
|
|
|
SDL_GLContext glCtx;
|
|
|
|
|
|
|
|
int frameRate;
|
|
|
|
int frameCount;
|
|
|
|
int brightness;
|
|
|
|
|
2023-05-03 23:00:06 -04:00
|
|
|
double last_update;
|
2021-02-25 21:29:21 -05:00
|
|
|
|
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
FPSLimiter fpsLimiter;
|
|
|
|
|
|
|
|
// Can be set from Ruby. Takes priority over config setting.
|
|
|
|
bool useFrameSkip;
|
|
|
|
|
|
|
|
bool frozen;
|
|
|
|
TEXFBO frozenScene;
|
|
|
|
Quad screenQuad;
|
|
|
|
|
2022-07-04 09:40:57 -04:00
|
|
|
float backingScaleFactor;
|
|
|
|
|
2022-07-03 06:59:46 -04:00
|
|
|
Vec2i integerScaleFactor;
|
|
|
|
TEXFBO integerScaleBuffer;
|
|
|
|
bool integerScaleActive;
|
|
|
|
bool integerLastMileScaling;
|
|
|
|
|
2023-05-03 23:00:06 -04:00
|
|
|
std::vector<double> avgFPSData;
|
|
|
|
double last_avg_update;
|
2021-03-01 01:51:15 -05:00
|
|
|
SDL_mutex *avgFPSLock;
|
2021-02-27 08:17:14 -05:00
|
|
|
|
2021-06-02 08:42:32 -04:00
|
|
|
SDL_mutex *glResourceLock;
|
2022-07-16 20:15:09 -04:00
|
|
|
bool multithreadedMode;
|
2021-06-02 08:42:32 -04:00
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
/* Global list of all live Disposables
|
|
|
|
* (disposed on reset) */
|
|
|
|
IntruList<Disposable> dispList;
|
|
|
|
|
|
|
|
GraphicsPrivate(RGSSThreadData *rtData)
|
2023-05-28 22:03:28 +00:00
|
|
|
: scRes(DEF_SCREEN_W, DEF_SCREEN_H), scResLores(scRes), scSize(scRes),
|
2021-02-25 19:28:44 -05:00
|
|
|
winSize(rtData->config.defScreenW, rtData->config.defScreenH),
|
|
|
|
screen(scRes.x, scRes.y), threadData(rtData),
|
2022-07-16 20:15:09 -04:00
|
|
|
glCtx(SDL_GL_GetCurrentContext()), multithreadedMode(true),
|
|
|
|
frameRate(DEF_FRAMERATE), frameCount(0), brightness(255),
|
|
|
|
fpsLimiter(frameRate), useFrameSkip(rtData->config.frameSkip), frozen(false),
|
2022-07-04 09:40:57 -04:00
|
|
|
last_update(0), last_avg_update(0), backingScaleFactor(1), integerScaleFactor(0, 0),
|
2022-07-03 06:59:46 -04:00
|
|
|
integerScaleActive(rtData->config.integerScaling.active),
|
|
|
|
integerLastMileScaling(rtData->config.integerScaling.lastMileScaling) {
|
2023-05-03 23:00:06 -04:00
|
|
|
avgFPSData = std::vector<double>();
|
2021-03-01 01:51:15 -05:00
|
|
|
avgFPSLock = SDL_CreateMutex();
|
2021-06-02 08:42:32 -04:00
|
|
|
glResourceLock = SDL_CreateMutex();
|
2021-02-27 08:17:14 -05:00
|
|
|
|
2022-07-03 06:59:46 -04:00
|
|
|
if (integerScaleActive) {
|
2022-07-05 00:02:24 -04:00
|
|
|
integerScaleFactor = Vec2i(0, 0);
|
2022-07-03 06:59:46 -04:00
|
|
|
rebuildIntegerScaleBuffer();
|
|
|
|
}
|
2022-07-12 00:39:34 -04:00
|
|
|
|
2023-05-10 22:22:50 -04:00
|
|
|
recalculateScreenSize(rtData->config.fixedAspectRatio);
|
2021-02-25 19:28:44 -05:00
|
|
|
updateScreenResoRatio(rtData);
|
|
|
|
|
|
|
|
TEXFBO::init(frozenScene);
|
|
|
|
TEXFBO::allocEmpty(frozenScene, scRes.x, scRes.y);
|
|
|
|
TEXFBO::linkFBO(frozenScene);
|
|
|
|
|
|
|
|
FloatRect screenRect(0, 0, scRes.x, scRes.y);
|
|
|
|
screenQuad.setTexPosRect(screenRect, screenRect);
|
|
|
|
|
|
|
|
fpsLimiter.resetFrameAdjust();
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
2021-03-01 01:51:15 -05:00
|
|
|
~GraphicsPrivate() {
|
|
|
|
TEXFBO::fini(frozenScene);
|
2022-07-03 06:59:46 -04:00
|
|
|
TEXFBO::fini(integerScaleBuffer);
|
2021-03-01 01:51:15 -05:00
|
|
|
SDL_DestroyMutex(avgFPSLock);
|
2021-06-02 08:42:32 -04:00
|
|
|
SDL_DestroyMutex(glResourceLock);
|
2021-03-01 01:51:15 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
void updateScreenResoRatio(RGSSThreadData *rtData) {
|
|
|
|
Vec2 &ratio = rtData->sizeResoRatio;
|
2022-07-04 09:40:57 -04:00
|
|
|
ratio.x = (float)scRes.x / scSize.x * backingScaleFactor;
|
|
|
|
ratio.y = (float)scRes.y / scSize.y * backingScaleFactor;
|
2021-02-25 19:28:44 -05:00
|
|
|
|
2022-07-04 11:18:22 -04:00
|
|
|
rtData->screenOffset = scOffset / backingScaleFactor;
|
2021-02-25 19:28:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Enforces fixed aspect ratio, if desired */
|
2022-07-03 06:59:46 -04:00
|
|
|
void recalculateScreenSize(bool fixedAspectRatio) {
|
2021-02-25 19:28:44 -05:00
|
|
|
scSize = winSize;
|
|
|
|
|
2023-05-10 22:22:50 -04:00
|
|
|
if (!fixedAspectRatio) {
|
|
|
|
if (!integerScaleActive || (integerScaleActive && integerLastMileScaling)) {
|
|
|
|
scOffset = Vec2i(0, 0);
|
|
|
|
return;
|
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
}
|
|
|
|
|
2022-07-03 06:59:46 -04:00
|
|
|
if (integerScaleActive && !integerLastMileScaling) {
|
2022-07-05 00:02:24 -04:00
|
|
|
scOffset.x = ((winSize.x / 2) - (scRes.x / 2) * integerScaleFactor.x);
|
|
|
|
scOffset.y = ((winSize.y / 2) - (scRes.y / 2) * integerScaleFactor.y);
|
2022-07-03 06:59:46 -04:00
|
|
|
|
|
|
|
scSize = Vec2i(scRes.x * integerScaleFactor.x, scRes.y * integerScaleFactor.y);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
float resRatio = (float)scRes.x / scRes.y;
|
|
|
|
float winRatio = (float)winSize.x / winSize.y;
|
|
|
|
|
|
|
|
if (resRatio > winRatio)
|
|
|
|
scSize.y = scSize.x / resRatio;
|
|
|
|
else if (resRatio < winRatio)
|
|
|
|
scSize.x = scSize.y * resRatio;
|
|
|
|
|
|
|
|
scOffset.x = (winSize.x - scSize.x) / 2.f;
|
|
|
|
scOffset.y = (winSize.y - scSize.y) / 2.f;
|
|
|
|
}
|
|
|
|
|
2022-07-03 06:59:46 -04:00
|
|
|
static int findHighestFittingScale(int base, int target) {
|
|
|
|
int scale = 1;
|
|
|
|
|
|
|
|
while (base * scale <= target)
|
|
|
|
scale++;
|
|
|
|
|
2022-07-05 00:02:24 -04:00
|
|
|
return std::max(scale - 1, 1);
|
2022-07-03 06:59:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns whether a new scale was found */
|
|
|
|
bool findHighestIntegerScale()
|
|
|
|
{
|
|
|
|
Vec2i newScale(findHighestFittingScale(scRes.x, winSize.x),
|
|
|
|
findHighestFittingScale(scRes.y, winSize.y));
|
|
|
|
|
|
|
|
if (threadData->config.fixedAspectRatio)
|
|
|
|
{
|
|
|
|
/* Limit both factors to the smaller of the two */
|
|
|
|
newScale.x = newScale.y = std::min(newScale.x, newScale.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newScale == integerScaleFactor)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
integerScaleFactor = newScale;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void rebuildIntegerScaleBuffer()
|
|
|
|
{
|
|
|
|
TEXFBO::fini(integerScaleBuffer);
|
|
|
|
TEXFBO::init(integerScaleBuffer);
|
|
|
|
TEXFBO::allocEmpty(integerScaleBuffer, scRes.x * integerScaleFactor.x,
|
|
|
|
scRes.y * integerScaleFactor.y);
|
|
|
|
TEXFBO::linkFBO(integerScaleBuffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool integerScaleStepApplicable() const
|
|
|
|
{
|
|
|
|
if (!integerScaleActive)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (integerScaleFactor.x < 1 || integerScaleFactor.y < 1) // XXX should be < 2, this is for testing only
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-07-12 00:39:34 -04:00
|
|
|
void checkResize(bool skipIntScaleBuffer = false) {
|
2021-02-25 19:28:44 -05:00
|
|
|
if (threadData->windowSizeMsg.poll(winSize)) {
|
2022-07-03 06:59:46 -04:00
|
|
|
/* Query the actual size in pixels, not units */
|
2022-07-04 09:40:57 -04:00
|
|
|
Vec2i drawableSize(winSize);
|
|
|
|
threadData->drawableSizeMsg.poll(drawableSize);
|
|
|
|
|
|
|
|
backingScaleFactor = drawableSize.x / winSize.x;
|
|
|
|
winSize = drawableSize;
|
2022-07-03 06:59:46 -04:00
|
|
|
|
|
|
|
/* Make sure integer buffers are rebuilt before screen offsets are
|
|
|
|
* calculated so we have the final allocated buffer size ready */
|
2022-07-12 00:39:34 -04:00
|
|
|
if (integerScaleActive && findHighestIntegerScale() && !skipIntScaleBuffer)
|
2022-07-03 06:59:46 -04:00
|
|
|
rebuildIntegerScaleBuffer();
|
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
/* some GL drivers change the viewport on window resize */
|
|
|
|
glState.viewport.refresh();
|
2023-05-10 22:22:50 -04:00
|
|
|
recalculateScreenSize(threadData->config.fixedAspectRatio);
|
2021-02-25 19:28:44 -05:00
|
|
|
updateScreenResoRatio(threadData);
|
|
|
|
|
|
|
|
SDL_Rect screen = {scOffset.x, scOffset.y, scSize.x, scSize.y};
|
|
|
|
threadData->ethread->notifyGameScreenChange(screen);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void checkShutDownReset() {
|
|
|
|
shState->checkShutdown();
|
|
|
|
shState->checkReset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void shutdown() {
|
|
|
|
threadData->rqTermAck.set();
|
|
|
|
shState->texPool().disable();
|
|
|
|
|
|
|
|
scriptBinding->terminate();
|
|
|
|
}
|
|
|
|
|
|
|
|
void swapGLBuffer() {
|
|
|
|
fpsLimiter.delay();
|
|
|
|
SDL_GL_SwapWindow(threadData->window);
|
|
|
|
|
|
|
|
++frameCount;
|
|
|
|
|
|
|
|
threadData->ethread->notifyFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
void compositeToBuffer(TEXFBO &buffer) {
|
2023-05-28 22:03:28 +00:00
|
|
|
compositeToBufferScaled(buffer, scRes.x, scRes.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
void compositeToBufferScaled(TEXFBO &buffer, int destWidth, int destHeight) {
|
2021-02-25 19:28:44 -05:00
|
|
|
screen.composite();
|
|
|
|
|
|
|
|
GLMeta::blitBegin(buffer);
|
|
|
|
GLMeta::blitSource(screen.getPP().frontBuffer());
|
2023-05-28 22:03:28 +00:00
|
|
|
GLMeta::blitRectangle(IntRect(0, 0, scRes.x, scRes.y), IntRect(0, 0, destWidth, destHeight));
|
2021-02-25 19:28:44 -05:00
|
|
|
GLMeta::blitEnd();
|
|
|
|
}
|
|
|
|
|
|
|
|
void metaBlitBufferFlippedScaled() {
|
2022-07-03 06:59:46 -04:00
|
|
|
metaBlitBufferFlippedScaled(scRes);
|
2021-02-25 19:28:44 -05:00
|
|
|
GLMeta::blitRectangle(
|
|
|
|
IntRect(0, 0, scRes.x, scRes.y),
|
2022-07-05 00:02:24 -04:00
|
|
|
IntRect(scOffset.x,
|
|
|
|
(scSize.y + scOffset.y),
|
|
|
|
scSize.x,
|
|
|
|
-scSize.y),
|
2023-10-26 18:35:39 +00:00
|
|
|
threadData->config.smoothScaling == Bilinear);
|
2021-02-25 19:28:44 -05:00
|
|
|
}
|
|
|
|
|
2022-07-03 06:59:46 -04:00
|
|
|
void metaBlitBufferFlippedScaled(const Vec2i &sourceSize, bool forceNearestNeighbor=false) {
|
|
|
|
GLMeta::blitRectangle(IntRect(0, 0, sourceSize.x, sourceSize.y),
|
|
|
|
IntRect(scOffset.x, scSize.y+scOffset.y, scSize.x, -scSize.y),
|
2023-10-26 18:35:39 +00:00
|
|
|
!forceNearestNeighbor && threadData->config.smoothScaling == Bilinear);
|
2022-07-03 06:59:46 -04:00
|
|
|
}
|
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
void redrawScreen() {
|
|
|
|
screen.composite();
|
|
|
|
|
2022-07-03 06:59:46 -04:00
|
|
|
// maybe unspaghetti this later
|
|
|
|
if (integerScaleStepApplicable() && !integerLastMileScaling)
|
|
|
|
{
|
|
|
|
GLMeta::blitBeginScreen(winSize);
|
|
|
|
GLMeta::blitSource(screen.getPP().frontBuffer());
|
|
|
|
|
|
|
|
FBO::clear();
|
|
|
|
metaBlitBufferFlippedScaled(scRes, true);
|
|
|
|
GLMeta::blitEnd();
|
|
|
|
|
|
|
|
swapGLBuffer();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (integerScaleStepApplicable())
|
|
|
|
{
|
|
|
|
assert(integerScaleBuffer.tex != TEX::ID(0));
|
|
|
|
GLMeta::blitBegin(integerScaleBuffer);
|
|
|
|
GLMeta::blitSource(screen.getPP().frontBuffer());
|
|
|
|
|
|
|
|
GLMeta::blitRectangle(IntRect(0, 0, scRes.x, scRes.y),
|
|
|
|
IntRect(0, 0, integerScaleBuffer.width, integerScaleBuffer.height),
|
|
|
|
false);
|
|
|
|
|
|
|
|
GLMeta::blitEnd();
|
|
|
|
}
|
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
GLMeta::blitBeginScreen(winSize);
|
2022-07-03 06:59:46 -04:00
|
|
|
//GLMeta::blitSource(screen.getPP().frontBuffer());
|
|
|
|
|
|
|
|
Vec2i sourceSize;
|
|
|
|
|
|
|
|
if (integerScaleActive)
|
|
|
|
{
|
|
|
|
GLMeta::blitSource(integerScaleBuffer);
|
|
|
|
sourceSize = Vec2i(integerScaleBuffer.width, integerScaleBuffer.height);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
GLMeta::blitSource(screen.getPP().frontBuffer());
|
|
|
|
sourceSize = scRes;
|
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
FBO::clear();
|
2022-07-03 06:59:46 -04:00
|
|
|
metaBlitBufferFlippedScaled(sourceSize);
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
GLMeta::blitEnd();
|
|
|
|
|
|
|
|
swapGLBuffer();
|
2021-02-27 08:17:14 -05:00
|
|
|
|
2021-03-01 01:51:15 -05:00
|
|
|
SDL_LockMutex(avgFPSLock);
|
|
|
|
if (avgFPSData.size() > 40)
|
2021-02-27 08:17:14 -05:00
|
|
|
avgFPSData.erase(avgFPSData.begin());
|
|
|
|
|
2023-05-03 23:00:06 -04:00
|
|
|
double time = shState->runTime();
|
2021-03-01 03:45:34 -05:00
|
|
|
avgFPSData.push_back(time - last_avg_update);
|
|
|
|
last_avg_update = time;
|
2021-03-01 01:51:15 -05:00
|
|
|
SDL_UnlockMutex(avgFPSLock);
|
2021-02-25 19:28:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void checkSyncLock() {
|
|
|
|
if (!threadData->syncPoint.mainSyncLocked())
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Releasing the GL context before sleeping and making it
|
|
|
|
* current again on wakeup seems to avoid the context loss
|
|
|
|
* when the app moves into the background on Android */
|
|
|
|
SDL_GL_MakeCurrent(threadData->window, 0);
|
|
|
|
threadData->syncPoint.waitMainSync();
|
|
|
|
SDL_GL_MakeCurrent(threadData->window, glCtx);
|
|
|
|
|
|
|
|
fpsLimiter.resetFrameAdjust();
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-27 08:17:14 -05:00
|
|
|
|
|
|
|
double averageFPS() {
|
2021-05-21 16:28:51 -04:00
|
|
|
double ret = 0;
|
2021-03-01 01:51:15 -05:00
|
|
|
SDL_LockMutex(avgFPSLock);
|
2023-05-03 23:00:06 -04:00
|
|
|
for (double times : avgFPSData)
|
2021-02-27 08:17:14 -05:00
|
|
|
ret += times;
|
|
|
|
|
2023-05-03 23:00:06 -04:00
|
|
|
ret = 1 / (ret / avgFPSData.size());
|
2021-03-01 01:51:15 -05:00
|
|
|
SDL_UnlockMutex(avgFPSLock);
|
|
|
|
return ret;
|
2021-02-27 08:17:14 -05:00
|
|
|
}
|
2021-06-02 08:42:32 -04:00
|
|
|
|
2022-07-16 20:15:09 -04:00
|
|
|
void setLock(bool force = false) {
|
|
|
|
if (!(force || multithreadedMode)) return;
|
|
|
|
|
2021-06-02 08:42:32 -04:00
|
|
|
SDL_LockMutex(glResourceLock);
|
2021-06-04 14:29:45 -04:00
|
|
|
SDL_GL_MakeCurrent(threadData->window, threadData->glContext);
|
2021-06-02 08:42:32 -04:00
|
|
|
}
|
|
|
|
|
2022-07-16 20:15:09 -04:00
|
|
|
void releaseLock(bool force = false) {
|
|
|
|
if (!(force || multithreadedMode)) return;
|
|
|
|
|
2021-06-02 08:42:32 -04:00
|
|
|
SDL_UnlockMutex(glResourceLock);
|
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
};
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
Graphics::Graphics(RGSSThreadData *data) {
|
2021-02-25 19:28:44 -05:00
|
|
|
p = new GraphicsPrivate(data);
|
|
|
|
if (data->config.syncToRefreshrate) {
|
|
|
|
p->frameRate = data->refreshRate;
|
|
|
|
p->fpsLimiter.disabled = true;
|
|
|
|
} else if (data->config.fixedFramerate > 0) {
|
|
|
|
p->fpsLimiter.setDesiredFPS(data->config.fixedFramerate);
|
|
|
|
} else if (data->config.fixedFramerate < 0) {
|
|
|
|
p->fpsLimiter.disabled = true;
|
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
}
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
Graphics::~Graphics() { delete p; }
|
|
|
|
|
2023-05-03 23:00:06 -04:00
|
|
|
double Graphics::getDelta() {
|
2021-02-25 21:29:21 -05:00
|
|
|
return shState->runTime() - p->last_update;
|
|
|
|
}
|
|
|
|
|
2023-05-03 23:00:06 -04:00
|
|
|
double Graphics::lastUpdate() {
|
2021-05-04 23:32:01 -04:00
|
|
|
return p->last_update;
|
|
|
|
}
|
|
|
|
|
2022-01-23 01:46:13 -05:00
|
|
|
void Graphics::update(bool checkForShutdown) {
|
2021-09-30 18:50:06 -04:00
|
|
|
p->threadData->rqWindowAdjust.wait();
|
2021-03-01 03:45:34 -05:00
|
|
|
p->last_update = shState->runTime();
|
2022-01-23 01:46:13 -05:00
|
|
|
|
2023-07-06 23:58:18 -04:00
|
|
|
// update Input.repeat timing, rounding the framerate to the nearest 2
|
|
|
|
{
|
|
|
|
static const double mult = 2.0;
|
|
|
|
double afr = std::abs(averageFrameRate()); // abs shouldn't be necessary but that's ok
|
|
|
|
afr += mult / 2;
|
|
|
|
afr -= std::fmod(afr, mult);
|
|
|
|
shState->input().recalcRepeat(std::floor(afr));
|
|
|
|
}
|
|
|
|
|
2022-01-23 01:46:13 -05:00
|
|
|
if (checkForShutdown)
|
|
|
|
p->checkShutDownReset();
|
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
p->checkSyncLock();
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
|
2020-12-31 14:50:07 -05:00
|
|
|
#ifdef MKXPZ_STEAM
|
2021-02-25 19:28:44 -05:00
|
|
|
if (STEAMSHIM_alive())
|
|
|
|
STEAMSHIM_pump();
|
2020-02-28 03:23:16 -05:00
|
|
|
#endif
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
if (p->frozen)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (p->fpsLimiter.frameSkipRequired()) {
|
|
|
|
if (p->useFrameSkip) {
|
|
|
|
/* Skip frame */
|
|
|
|
p->fpsLimiter.delay();
|
|
|
|
++p->frameCount;
|
|
|
|
p->threadData->ethread->notifyFrame();
|
|
|
|
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
/* Just reset frame adjust counter */
|
|
|
|
p->fpsLimiter.resetFrameAdjust();
|
|
|
|
}
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
p->checkResize();
|
|
|
|
p->redrawScreen();
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::freeze() {
|
2021-02-25 19:28:44 -05:00
|
|
|
p->frozen = true;
|
|
|
|
|
|
|
|
p->checkShutDownReset();
|
|
|
|
p->checkResize();
|
|
|
|
|
|
|
|
/* Capture scene into frozen buffer */
|
|
|
|
p->compositeToBuffer(p->frozenScene);
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::transition(int duration, const char *filename, int vague) {
|
2020-02-26 13:28:51 -05:00
|
|
|
p->checkSyncLock();
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
if (!p->frozen)
|
|
|
|
return;
|
|
|
|
|
|
|
|
vague = clamp(vague, 1, 256);
|
|
|
|
Bitmap *transMap = *filename ? new Bitmap(filename) : 0;
|
|
|
|
|
|
|
|
setBrightness(255);
|
|
|
|
|
|
|
|
/* Capture new scene */
|
|
|
|
p->screen.composite();
|
|
|
|
|
|
|
|
/* The PP frontbuffer will hold the current scene after the
|
|
|
|
* composition step. Since the backbuffer is unused during
|
|
|
|
* the transition, we can reuse it as the target buffer for
|
|
|
|
* the final rendered image. */
|
|
|
|
TEXFBO ¤tScene = p->screen.getPP().frontBuffer();
|
|
|
|
TEXFBO &transBuffer = p->screen.getPP().backBuffer();
|
|
|
|
|
|
|
|
/* If no transition bitmap is provided,
|
|
|
|
* we can use a simplified shader */
|
|
|
|
TransShader &transShader = shState->shaders().trans;
|
|
|
|
SimpleTransShader &simpleShader = shState->shaders().simpleTrans;
|
|
|
|
|
2023-05-28 22:03:28 +00:00
|
|
|
// Handle high-res.
|
|
|
|
Vec2i transSize(p->scResLores.x, p->scResLores.y);
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
if (transMap) {
|
2021-02-25 19:28:44 -05:00
|
|
|
TransShader &shader = transShader;
|
|
|
|
shader.bind();
|
|
|
|
shader.applyViewportProj();
|
|
|
|
shader.setFrozenScene(p->frozenScene.tex);
|
|
|
|
shader.setCurrentScene(currentScene.tex);
|
2023-05-28 22:03:28 +00:00
|
|
|
if (transMap->hasHires()) {
|
|
|
|
Debug() << "BUG: High-res Graphics transMap not implemented";
|
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
shader.setTransMap(transMap->getGLTypes().tex);
|
|
|
|
shader.setVague(vague / 256.0f);
|
2023-05-28 22:03:28 +00:00
|
|
|
shader.setTexSize(transSize);
|
2020-02-26 13:28:51 -05:00
|
|
|
} else {
|
2021-02-25 19:28:44 -05:00
|
|
|
SimpleTransShader &shader = simpleShader;
|
|
|
|
shader.bind();
|
|
|
|
shader.applyViewportProj();
|
|
|
|
shader.setFrozenScene(p->frozenScene.tex);
|
|
|
|
shader.setCurrentScene(currentScene.tex);
|
2023-05-28 22:03:28 +00:00
|
|
|
shader.setTexSize(transSize);
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
glState.blend.pushSet(false);
|
|
|
|
|
|
|
|
for (int i = 0; i < duration; ++i) {
|
|
|
|
/* We need to clean up transMap properly before
|
|
|
|
* a possible longjmp, so we manually test for
|
|
|
|
* shutdown/reset here */
|
|
|
|
if (p->threadData->rqTerm) {
|
|
|
|
glState.blend.pop();
|
|
|
|
delete transMap;
|
|
|
|
p->shutdown();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p->threadData->rqReset) {
|
|
|
|
glState.blend.pop();
|
|
|
|
delete transMap;
|
|
|
|
scriptBinding->reset();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
p->checkSyncLock();
|
|
|
|
|
|
|
|
const float prog = i * (1.0f / duration);
|
|
|
|
|
|
|
|
if (transMap) {
|
|
|
|
transShader.bind();
|
|
|
|
transShader.setProg(prog);
|
|
|
|
} else {
|
|
|
|
simpleShader.bind();
|
|
|
|
simpleShader.setProg(prog);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Draw the composed frame to a buffer first
|
|
|
|
* (we need this because we're skipping PingPong) */
|
|
|
|
FBO::bind(transBuffer.fbo);
|
|
|
|
FBO::clear();
|
|
|
|
p->screenQuad.draw();
|
|
|
|
|
|
|
|
p->checkResize();
|
|
|
|
|
|
|
|
/* Then blit it flipped and scaled to the screen */
|
|
|
|
FBO::unbind();
|
|
|
|
FBO::clear();
|
|
|
|
|
|
|
|
GLMeta::blitBeginScreen(Vec2i(p->winSize));
|
|
|
|
GLMeta::blitSource(transBuffer);
|
|
|
|
p->metaBlitBufferFlippedScaled();
|
|
|
|
GLMeta::blitEnd();
|
|
|
|
|
|
|
|
p->swapGLBuffer();
|
|
|
|
}
|
|
|
|
|
|
|
|
glState.blend.pop();
|
|
|
|
|
|
|
|
delete transMap;
|
|
|
|
|
|
|
|
p->frozen = false;
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::frameReset() {p->fpsLimiter.resetFrameAdjust();}
|
2020-02-26 13:28:51 -05:00
|
|
|
|
2014-09-23 21:12:58 +02:00
|
|
|
static void guardDisposed() {}
|
2013-10-06 09:55:27 +02:00
|
|
|
|
|
|
|
DEF_ATTR_RD_SIMPLE(Graphics, FrameRate, int, p->frameRate)
|
|
|
|
|
|
|
|
DEF_ATTR_SIMPLE(Graphics, FrameCount, int, p->frameCount)
|
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::setFrameRate(int value) {
|
2021-02-25 19:28:44 -05:00
|
|
|
p->frameRate = clamp(value, 10, 120);
|
|
|
|
|
|
|
|
if (p->threadData->config.syncToRefreshrate)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (p->threadData->config.fixedFramerate > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
p->fpsLimiter.setDesiredFPS(p->frameRate);
|
2023-07-06 23:58:18 -04:00
|
|
|
//shState->input().recalcRepeat((unsigned int)p->frameRate);
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2021-02-27 08:17:14 -05:00
|
|
|
double Graphics::averageFrameRate() {
|
|
|
|
return p->averageFPS();
|
|
|
|
}
|
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::wait(int duration) {
|
2021-02-25 19:28:44 -05:00
|
|
|
for (int i = 0; i < duration; ++i) {
|
|
|
|
p->checkShutDownReset();
|
|
|
|
p->redrawScreen();
|
|
|
|
}
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-10-06 09:55:27 +02:00
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::fadeout(int duration) {
|
2021-02-25 19:28:44 -05:00
|
|
|
FBO::unbind();
|
|
|
|
|
|
|
|
float curr = p->brightness;
|
|
|
|
float diff = 255.0f - curr;
|
|
|
|
|
|
|
|
for (int i = duration - 1; i > -1; --i) {
|
|
|
|
setBrightness(diff + (curr / duration) * i);
|
|
|
|
|
|
|
|
if (p->frozen) {
|
|
|
|
GLMeta::blitBeginScreen(p->scSize);
|
|
|
|
GLMeta::blitSource(p->frozenScene);
|
|
|
|
|
|
|
|
FBO::clear();
|
|
|
|
p->metaBlitBufferFlippedScaled();
|
|
|
|
|
|
|
|
GLMeta::blitEnd();
|
|
|
|
|
|
|
|
p->swapGLBuffer();
|
|
|
|
} else {
|
|
|
|
update();
|
|
|
|
}
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-10-06 09:55:27 +02:00
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::fadein(int duration) {
|
2021-02-25 19:28:44 -05:00
|
|
|
FBO::unbind();
|
|
|
|
|
|
|
|
float curr = p->brightness;
|
|
|
|
float diff = 255.0f - curr;
|
|
|
|
|
|
|
|
for (int i = 1; i <= duration; ++i) {
|
|
|
|
setBrightness(curr + (diff / duration) * i);
|
|
|
|
|
|
|
|
if (p->frozen) {
|
|
|
|
GLMeta::blitBeginScreen(p->scSize);
|
|
|
|
GLMeta::blitSource(p->frozenScene);
|
|
|
|
|
|
|
|
FBO::clear();
|
|
|
|
p->metaBlitBufferFlippedScaled();
|
|
|
|
|
|
|
|
GLMeta::blitEnd();
|
|
|
|
|
|
|
|
p->swapGLBuffer();
|
|
|
|
} else {
|
|
|
|
update();
|
|
|
|
}
|
2020-02-26 13:28:51 -05:00
|
|
|
}
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-10-06 09:55:27 +02:00
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
Bitmap *Graphics::snapToBitmap() {
|
2021-02-25 19:28:44 -05:00
|
|
|
Bitmap *bitmap = new Bitmap(width(), height());
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-05-28 22:03:28 +00:00
|
|
|
if (bitmap->hasHires()) {
|
|
|
|
p->compositeToBufferScaled(bitmap->getHires()->getGLTypes(), bitmap->getHires()->width(), bitmap->getHires()->height());
|
|
|
|
}
|
|
|
|
|
|
|
|
p->compositeToBufferScaled(bitmap->getGLTypes(), bitmap->width(), bitmap->height());
|
2021-02-25 19:28:44 -05:00
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
/* Taint entire bitmap */
|
|
|
|
bitmap->taintArea(IntRect(0, 0, width(), height()));
|
2021-02-25 19:28:44 -05:00
|
|
|
return bitmap;
|
2013-09-01 16:27:21 +02:00
|
|
|
}
|
|
|
|
|
2023-05-28 22:03:28 +00:00
|
|
|
int Graphics::width() const { return p->scResLores.x; }
|
|
|
|
|
|
|
|
int Graphics::height() const { return p->scResLores.y; }
|
|
|
|
|
|
|
|
int Graphics::widthHires() const { return p->scRes.x; }
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2023-05-28 22:03:28 +00:00
|
|
|
int Graphics::heightHires() const { return p->scRes.y; }
|
|
|
|
|
|
|
|
bool Graphics::isPingPongFramebufferActive() const {
|
|
|
|
return p->screen.getPP().frontBuffer().fbo == FBO::boundFramebufferID || p->screen.getPP().backBuffer().fbo == FBO::boundFramebufferID;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Graphics::displayContentWidth() const {
|
|
|
|
return p->scSize.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Graphics::displayContentHeight() const {
|
|
|
|
return p->scSize.y;
|
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2022-09-12 17:04:47 -04:00
|
|
|
int Graphics::displayWidth() const {
|
|
|
|
SDL_DisplayMode dm{};
|
|
|
|
SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(shState->sdlWindow()), &dm);
|
|
|
|
return dm.w / p->backingScaleFactor;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Graphics::displayHeight() const {
|
|
|
|
SDL_DisplayMode dm{};
|
|
|
|
SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(shState->sdlWindow()), &dm);
|
|
|
|
return dm.h / p->backingScaleFactor;
|
|
|
|
}
|
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::resizeScreen(int width, int height) {
|
2021-05-21 17:13:53 -04:00
|
|
|
p->threadData->rqWindowAdjust.wait();
|
2022-07-12 00:39:34 -04:00
|
|
|
p->checkResize(true);
|
2022-07-09 05:12:41 -04:00
|
|
|
|
2023-05-28 22:03:28 +00:00
|
|
|
Vec2i sizeLores(width, height);
|
|
|
|
|
|
|
|
if (shState->config().enableHires) {
|
|
|
|
double framebufferScalingFactor = shState->config().framebufferScalingFactor;
|
|
|
|
width = (int)lround(framebufferScalingFactor * width);
|
|
|
|
height = (int)lround(framebufferScalingFactor * height);
|
|
|
|
}
|
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
Vec2i size(width, height);
|
|
|
|
|
|
|
|
if (p->scRes == size)
|
|
|
|
return;
|
|
|
|
|
|
|
|
p->scRes = size;
|
2023-05-28 22:03:28 +00:00
|
|
|
p->scResLores = sizeLores;
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
p->screen.setResolution(width, height);
|
2022-07-12 00:54:32 -04:00
|
|
|
|
|
|
|
if (p->integerScaleActive)
|
|
|
|
p->rebuildIntegerScaleBuffer();
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
TEXFBO::allocEmpty(p->frozenScene, width, height);
|
|
|
|
|
|
|
|
FloatRect screenRect(0, 0, width, height);
|
|
|
|
p->screenQuad.setTexPosRect(screenRect, screenRect);
|
|
|
|
|
|
|
|
glState.scissorBox.set(IntRect(0, 0, p->scRes.x, p->scRes.y));
|
|
|
|
|
|
|
|
shState->eThread().requestWindowResize(width, height);
|
2022-07-05 08:41:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::resizeWindow(int width, int height, bool center) {
|
|
|
|
p->threadData->rqWindowAdjust.wait();
|
2022-07-09 05:12:41 -04:00
|
|
|
p->checkResize();
|
2022-07-05 08:41:51 -04:00
|
|
|
|
|
|
|
if (width == p->winSize.x / p->backingScaleFactor &&
|
|
|
|
height == p->winSize.y / p->backingScaleFactor)
|
|
|
|
return;
|
2022-07-24 03:50:06 -04:00
|
|
|
|
2022-07-05 08:41:51 -04:00
|
|
|
shState->eThread().requestWindowResize(width, height);
|
|
|
|
|
|
|
|
if (center)
|
|
|
|
this->center();
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
bool Graphics::updateMovieInput(Movie *movie) {
|
|
|
|
return p->threadData->rqTerm || p->threadData->rqReset;
|
2022-01-06 19:32:13 -05:00
|
|
|
}
|
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
void Graphics::playMovie(const char *filename, int volume_, bool skippable) {
|
2023-05-28 22:03:28 +00:00
|
|
|
if (shState->config().enableHires) {
|
|
|
|
Debug() << "BUG: High-res Graphics playMovie not implemented";
|
|
|
|
}
|
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
Movie *movie = new Movie(skippable);
|
2022-01-13 22:20:43 -05:00
|
|
|
MovieOpenHandler handler(movie->srcOps);
|
|
|
|
shState->fileSystem().openRead(handler, filename);
|
2023-02-08 21:53:10 -05:00
|
|
|
float volume = volume_ * 0.01f;
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
if (movie->preparePlayback()) {
|
2022-01-21 00:38:13 -05:00
|
|
|
Sprite movieSprite;
|
|
|
|
|
|
|
|
// Currently this stretches to fit the screen. VX Ace behavior is to center it and let the edges run off
|
|
|
|
movieSprite.setBitmap(movie->videoBitmap);
|
2022-01-23 06:20:13 -05:00
|
|
|
double ratio = std::min((double)width() / movie->video->width, (double)height() / movie->video->height);
|
2022-01-23 01:46:13 -05:00
|
|
|
movieSprite.setZoomX(ratio);
|
|
|
|
movieSprite.setZoomY(ratio);
|
2022-01-23 06:20:13 -05:00
|
|
|
movieSprite.setX((width() / 2) - (movie->video->width * ratio / 2));
|
2022-01-23 01:46:13 -05:00
|
|
|
movieSprite.setY((height() / 2) - (movie->video->height * ratio / 2));
|
|
|
|
|
|
|
|
Sprite letterboxSprite;
|
|
|
|
Bitmap letterbox(width(), height());
|
|
|
|
letterbox.fillRect(0, 0, width(), height(), Vec4(0,0,0,255));
|
|
|
|
letterboxSprite.setBitmap(&letterbox);
|
|
|
|
|
2022-03-19 20:43:17 +00:00
|
|
|
letterboxSprite.setZ(4999);
|
|
|
|
movieSprite.setZ(5001);
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2023-02-08 21:53:10 -05:00
|
|
|
movie->play(volume);
|
2022-01-02 18:05:17 -05:00
|
|
|
}
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-01-13 22:20:43 -05:00
|
|
|
delete movie;
|
2015-10-30 08:01:36 -07:00
|
|
|
}
|
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::screenshot(const char *filename) {
|
2021-06-02 08:42:32 -04:00
|
|
|
p->threadData->rqWindowAdjust.wait();
|
2021-02-25 19:28:44 -05:00
|
|
|
Bitmap *ss = snapToBitmap();
|
|
|
|
ss->saveToFile(filename);
|
|
|
|
ss->dispose();
|
|
|
|
delete ss;
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2019-08-19 08:59:35 -04:00
|
|
|
|
2013-09-01 16:27:21 +02:00
|
|
|
DEF_ATTR_RD_SIMPLE(Graphics, Brightness, int, p->brightness)
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
void Graphics::setBrightness(int value) {
|
2021-02-25 19:28:44 -05:00
|
|
|
value = clamp(value, 0, 255);
|
|
|
|
|
|
|
|
if (p->brightness == value)
|
|
|
|
return;
|
|
|
|
|
|
|
|
p->brightness = value;
|
|
|
|
p->screen.setBrightness(value / 255.0);
|
2013-09-01 16:27:21 +02:00
|
|
|
}
|
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::reset() {
|
2021-02-25 19:28:44 -05:00
|
|
|
/* Dispose all live Disposables */
|
|
|
|
IntruListLink<Disposable> *iter;
|
|
|
|
|
|
|
|
for (iter = p->dispList.begin(); iter != p->dispList.end();
|
|
|
|
iter = iter->next) {
|
|
|
|
iter->data->dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
p->dispList.clear();
|
|
|
|
|
|
|
|
/* Reset attributes (frame count not included) */
|
|
|
|
p->fpsLimiter.resetFrameAdjust();
|
|
|
|
p->frozen = false;
|
|
|
|
p->screen.getPP().clearBuffers();
|
|
|
|
|
|
|
|
setFrameRate(DEF_FRAMERATE);
|
|
|
|
setBrightness(255);
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2014-08-24 07:36:19 +02:00
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::center() {
|
2022-07-24 03:50:06 -04:00
|
|
|
p->threadData->rqWindowAdjust.wait();
|
2021-02-25 19:28:44 -05:00
|
|
|
if (getFullscreen())
|
|
|
|
return;
|
2021-05-21 17:13:53 -04:00
|
|
|
|
2021-02-25 19:28:44 -05:00
|
|
|
p->threadData->ethread->requestWindowCenter();
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2019-08-31 12:55:03 -04:00
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
bool Graphics::getFullscreen() const {
|
2021-02-25 19:28:44 -05:00
|
|
|
return p->threadData->ethread->getFullscreen();
|
2013-09-01 16:27:21 +02:00
|
|
|
}
|
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::setFullscreen(bool value) {
|
2021-02-25 19:28:44 -05:00
|
|
|
p->threadData->ethread->requestFullscreenMode(value);
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
bool Graphics::getShowCursor() const {
|
2021-02-25 19:28:44 -05:00
|
|
|
return p->threadData->ethread->getShowCursor();
|
2013-09-24 22:52:42 +02:00
|
|
|
}
|
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::setShowCursor(bool value) {
|
2021-02-25 19:28:44 -05:00
|
|
|
p->threadData->ethread->requestShowCursor(value);
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-09-24 22:52:42 +02:00
|
|
|
|
2022-07-03 06:59:46 -04:00
|
|
|
bool Graphics::getFixedAspectRatio() const
|
|
|
|
{
|
|
|
|
// It's a bit hacky to expose config values as a Graphics
|
|
|
|
// attribute, but there's really no point in state duplication
|
|
|
|
return shState->config().fixedAspectRatio;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::setFixedAspectRatio(bool value)
|
|
|
|
{
|
|
|
|
shState->config().fixedAspectRatio = value;
|
2023-05-10 22:22:50 -04:00
|
|
|
p->recalculateScreenSize(p->threadData->config.fixedAspectRatio);
|
2022-07-03 10:01:07 -04:00
|
|
|
p->findHighestIntegerScale();
|
|
|
|
p->recalculateScreenSize(p->threadData->config.fixedAspectRatio);
|
|
|
|
p->updateScreenResoRatio(p->threadData);
|
2022-07-03 06:59:46 -04:00
|
|
|
}
|
|
|
|
|
2023-10-26 18:35:39 +00:00
|
|
|
int Graphics::getSmoothScaling() const
|
2022-07-03 06:59:46 -04:00
|
|
|
{
|
|
|
|
// Same deal as with fixed aspect ratio
|
|
|
|
return shState->config().smoothScaling;
|
|
|
|
}
|
|
|
|
|
2023-10-26 18:35:39 +00:00
|
|
|
void Graphics::setSmoothScaling(int value)
|
2022-07-03 06:59:46 -04:00
|
|
|
{
|
|
|
|
shState->config().smoothScaling = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Graphics::getIntegerScaling() const
|
|
|
|
{
|
|
|
|
return p->integerScaleActive;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::setIntegerScaling(bool value)
|
|
|
|
{
|
|
|
|
p->integerScaleActive = value;
|
2022-07-03 10:01:07 -04:00
|
|
|
p->findHighestIntegerScale();
|
|
|
|
p->rebuildIntegerScaleBuffer();
|
2022-07-03 06:59:46 -04:00
|
|
|
|
2022-07-03 10:01:07 -04:00
|
|
|
p->recalculateScreenSize(p->threadData->config.fixedAspectRatio);
|
|
|
|
p->updateScreenResoRatio(p->threadData);
|
2022-07-03 06:59:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Graphics::getLastMileScaling() const
|
|
|
|
{
|
|
|
|
return p->integerLastMileScaling;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::setLastMileScaling(bool value)
|
|
|
|
{
|
|
|
|
p->integerLastMileScaling = value;
|
2022-07-03 10:01:07 -04:00
|
|
|
p->recalculateScreenSize(p->threadData->config.fixedAspectRatio);
|
|
|
|
p->updateScreenResoRatio(p->threadData);
|
2022-07-03 06:59:46 -04:00
|
|
|
}
|
|
|
|
|
2022-07-16 20:15:09 -04:00
|
|
|
bool Graphics::getThreadsafe() const
|
|
|
|
{
|
|
|
|
return p->multithreadedMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Graphics::setThreadsafe(bool value)
|
|
|
|
{
|
|
|
|
p->multithreadedMode = value;
|
|
|
|
}
|
|
|
|
|
2022-07-09 05:12:41 -04:00
|
|
|
double Graphics::getScale() const {
|
|
|
|
p->checkResize();
|
|
|
|
return (double)(p->winSize.y / p->backingScaleFactor) / p->scRes.y;
|
|
|
|
|
|
|
|
}
|
2019-08-31 00:02:09 -04:00
|
|
|
|
2021-06-04 14:29:45 -04:00
|
|
|
void Graphics::setScale(double factor) {
|
2022-07-24 03:50:06 -04:00
|
|
|
p->threadData->rqWindowAdjust.wait();
|
2022-07-09 05:12:41 -04:00
|
|
|
factor = clamp(factor, 0.5, 4.0);
|
|
|
|
|
|
|
|
if (factor == getScale())
|
|
|
|
return;
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
int widthpx = p->scRes.x * factor;
|
|
|
|
int heightpx = p->scRes.y * factor;
|
|
|
|
|
2022-07-09 05:12:41 -04:00
|
|
|
shState->eThread().requestWindowResize(widthpx, heightpx);
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2020-04-16 05:28:30 -04:00
|
|
|
bool Graphics::getFrameskip() const { return p->useFrameSkip; }
|
|
|
|
|
|
|
|
void Graphics::setFrameskip(bool value) { p->useFrameSkip = value; }
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
Scene *Graphics::getScreen() const { return &p->screen; }
|
2013-09-01 16:27:21 +02:00
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
void Graphics::repaintWait(const AtomicFlag &exitCond, bool checkReset) {
|
2021-02-25 19:28:44 -05:00
|
|
|
if (exitCond)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Repaint the screen with the last good frame we drew */
|
|
|
|
TEXFBO &lastFrame = p->screen.getPP().frontBuffer();
|
|
|
|
GLMeta::blitBeginScreen(p->winSize);
|
|
|
|
GLMeta::blitSource(lastFrame);
|
|
|
|
|
|
|
|
while (!exitCond) {
|
2022-01-23 09:31:27 -05:00
|
|
|
shState->checkShutdown();
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
if (checkReset)
|
2022-01-23 09:31:27 -05:00
|
|
|
shState->checkReset();
|
2021-02-25 19:28:44 -05:00
|
|
|
|
|
|
|
FBO::clear();
|
|
|
|
p->metaBlitBufferFlippedScaled();
|
|
|
|
SDL_GL_SwapWindow(p->threadData->window);
|
|
|
|
p->fpsLimiter.delay();
|
|
|
|
|
|
|
|
p->threadData->ethread->notifyFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
GLMeta::blitEnd();
|
2014-08-24 07:36:19 +02:00
|
|
|
}
|
|
|
|
|
2022-07-16 20:15:09 -04:00
|
|
|
void Graphics::lock(bool force) {
|
|
|
|
p->setLock(force);
|
2021-06-04 14:29:45 -04:00
|
|
|
}
|
|
|
|
|
2022-07-16 20:15:09 -04:00
|
|
|
void Graphics::unlock(bool force) {
|
|
|
|
p->releaseLock(force);
|
2021-06-02 08:42:32 -04:00
|
|
|
}
|
|
|
|
|
2020-02-26 13:28:51 -05:00
|
|
|
void Graphics::addDisposable(Disposable *d) { p->dispList.append(d->link); }
|
|
|
|
|
|
|
|
void Graphics::remDisposable(Disposable *d) { p->dispList.remove(d->link); }
|
2021-06-02 08:42:32 -04:00
|
|
|
|
|
|
|
#undef GRAPHICS_THREAD_LOCK
|