scummvm/backends/platform/psp/display_manager.cpp
2021-12-26 18:48:43 +01:00

473 lines
14 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include <pspgu.h>
#include <pspdisplay.h>
#include <pspthreadman.h>
#include "common/scummsys.h"
#include "backends/base-backend.h"
#include "backends/platform/psp/psppixelformat.h"
#include "backends/platform/psp/display_client.h"
#include "backends/platform/psp/default_display_client.h"
#include "backends/platform/psp/cursor.h"
#include "backends/platform/psp/pspkeyboard.h"
#include "backends/platform/psp/image_viewer.h"
#include "common/config-manager.h"
#define USE_DISPLAY_CALLBACK // to use callback for finishing the render
#include "backends/platform/psp/display_manager.h"
#define PSP_BUFFER_WIDTH (512)
#define PSP_SCREEN_WIDTH 480
#define PSP_SCREEN_HEIGHT 272
#define PSP_FRAME_SIZE (PSP_BUFFER_WIDTH * PSP_SCREEN_HEIGHT)
//#define ENABLE_RENDER_MEASURE /* how long it takes to render a frame */
//#define __PSP_DEBUG_FUNCS__ /* For debugging function calls */
//#define __PSP_DEBUG_PRINT__ /* For debug printouts */
#include "backends/platform/psp/trace.h"
uint32 __attribute__((aligned(16))) MasterGuRenderer::_displayList[2048];
const OSystem::GraphicsMode DisplayManager::_supportedModes[] = {
{ "Original Resolution", "Original Resolution", ORIGINAL_RESOLUTION },
{ "Fit to Screen", "Fit to Screen", FIT_TO_SCREEN },
{ "Stretch to Screen", "Stretch to Screen", STRETCH_TO_SCREEN },
{0, 0, 0}
};
// Class VramAllocator -----------------------------------
namespace Common {
DECLARE_SINGLETON(VramAllocator);
}
//#define __PSP_DEBUG_FUNCS__ /* For debugging the stack */
//#define __PSP_DEBUG_PRINT__
#include "backends/platform/psp/trace.h"
void *VramAllocator::allocate(int32 size, bool smallAllocation /* = false */) {
DEBUG_ENTER_FUNC();
assert(size > 0);
byte *lastAddress = smallAllocation ? (byte *)VRAM_SMALL_ADDRESS : (byte *)VRAM_START_ADDRESS;
Common::List<Allocation>::iterator i;
// Find a block that fits, starting from the beginning
for (i = _allocList.begin(); i != _allocList.end(); ++i) {
byte *currAddress = (*i).address;
if (currAddress - lastAddress >= size) // We found a match
break;
if ((*i).getEnd() > lastAddress)
lastAddress = (byte *)(*i).getEnd();
}
if (lastAddress + size > (byte *)VRAM_END_ADDRESS) {
PSP_DEBUG_PRINT("No space for allocation of %d bytes. %d bytes already allocated.\n",
size, _bytesAllocated);
return NULL;
}
_allocList.insert(i, Allocation(lastAddress, size));
_bytesAllocated += size;
PSP_DEBUG_PRINT("Allocated in VRAM, size %u at %p.\n", size, lastAddress);
PSP_DEBUG_PRINT("Total allocated %u, remaining %u.\n", _bytesAllocated, (2 * 1024 * 1024) - _bytesAllocated);
return lastAddress;
}
// Deallocate a block from VRAM
void VramAllocator::deallocate(void *address) {
DEBUG_ENTER_FUNC();
address = (byte *)CACHED(address); // Make sure all addresses are the same
Common::List<Allocation>::iterator i;
// Find the Allocator to deallocate
for (i = _allocList.begin(); i != _allocList.end(); ++i) {
if ((*i).address == address) {
PSP_DEBUG_PRINT("Deallocated address[%p], size[%u]\n", (*i).address, (*i).size);
_bytesAllocated -= (*i).size;
_allocList.erase(i);
return;
}
}
PSP_DEBUG_PRINT("Address[%p] not allocated.\n", address);
}
// Class MasterGuRenderer ----------------------------------------------
void MasterGuRenderer::setupCallbackThread() {
DEBUG_ENTER_FUNC();
// start the thread that updates the display
threadCreateAndStart("DisplayCbThread", PRIORITY_DISPLAY_THREAD, STACK_DISPLAY_THREAD);
}
// this function gets called by PspThread when starting the new thread
void MasterGuRenderer::threadFunction() {
DEBUG_ENTER_FUNC();
// Create the callback. It should always get the pointer to MasterGuRenderer
_callbackId = sceKernelCreateCallback("Display Callback", guCallback, this);
if (_callbackId < 0) {
PSP_ERROR("failed to create display callback\n");
}
PSP_DEBUG_PRINT("created callback. Going to sleep\n");
sceKernelSleepThreadCB(); // sleep until we get a callback
}
// Sleep on the render mutex if the rendering thread hasn't finished its work
//
void MasterGuRenderer::sleepUntilRenderFinished() {
if (!isRenderFinished()) {
_renderSema.take(); // sleep on the semaphore
_renderSema.give();
PSP_DEBUG_PRINT("slept on the rendering semaphore\n");
}
}
// This callback is called when the render is finished. It swaps the buffers
int MasterGuRenderer::guCallback(int, int, void *__this) {
MasterGuRenderer *_this = (MasterGuRenderer *)__this;
sceGuSync(0, 0); // make sure we wait for GU to finish
sceDisplayWaitVblankStartCB(); // wait for v-blank without eating main thread cycles
sceGuSwapBuffers(); // swap the back and front buffers
_this->_renderFinished = true; // Only this thread can set the variable to true
_this->_renderSema.give(); // Release render semaphore
return 0;
}
void MasterGuRenderer::guInit() {
DEBUG_ENTER_FUNC();
sceGuInit();
sceGuStart(0, _displayList);
guProgramDisplayBufferSizes();
sceGuOffset(2048 - (PSP_SCREEN_WIDTH / 2), 2048 - (PSP_SCREEN_HEIGHT / 2));
sceGuViewport(2048, 2048, PSP_SCREEN_WIDTH, PSP_SCREEN_HEIGHT);
sceGuDepthRange(0xC350, 0x2710);
sceGuDisable(GU_DEPTH_TEST); // We'll use depth buffer area
sceGuDepthMask(GU_TRUE); // Prevent writes to depth buffer
sceGuScissor(0, 0, PSP_SCREEN_WIDTH, PSP_SCREEN_HEIGHT);
sceGuEnable(GU_SCISSOR_TEST);
sceGuFrontFace(GU_CW);
sceGuEnable(GU_TEXTURE_2D);
sceGuClear(GU_COLOR_BUFFER_BIT | GU_DEPTH_BUFFER_BIT);
sceGuFinish();
sceGuSync(0, 0);
sceDisplayWaitVblankStart();
sceGuDisplay(1);
}
void MasterGuRenderer::guProgramDisplayBufferSizes() {
DEBUG_ENTER_FUNC();
PSP_DEBUG_PRINT("Outputbits[%u]\n", GuRenderer::_displayManager->getOutputBitsPerPixel());
switch (GuRenderer::_displayManager->getOutputBitsPerPixel()) {
case 16:
sceGuDrawBuffer(GU_PSM_4444, (void *)0, PSP_BUFFER_WIDTH);
sceGuDispBuffer(PSP_SCREEN_WIDTH, PSP_SCREEN_HEIGHT, (void *)(PSP_FRAME_SIZE * sizeof(uint16)), PSP_BUFFER_WIDTH);
sceGuDepthBuffer((void *)(PSP_FRAME_SIZE * sizeof(uint16) * 2), PSP_BUFFER_WIDTH);
VramAllocator::instance().allocate(PSP_FRAME_SIZE * sizeof(uint16) * 2);
break;
case 32:
sceGuDrawBuffer(GU_PSM_8888, (void *)0, PSP_BUFFER_WIDTH);
sceGuDispBuffer(PSP_SCREEN_WIDTH, PSP_SCREEN_HEIGHT, (void *)(PSP_FRAME_SIZE * sizeof(uint32)), PSP_BUFFER_WIDTH);
sceGuDepthBuffer((void *)(PSP_FRAME_SIZE * sizeof(uint32) * 2), PSP_BUFFER_WIDTH);
VramAllocator::instance().allocate(PSP_FRAME_SIZE * sizeof(uint32) * 2);
break;
}
}
// These are GU commands that should always stay the same
inline void MasterGuRenderer::guPreRender() {
DEBUG_ENTER_FUNC();
#ifdef USE_DISPLAY_CALLBACK
_renderSema.take(); // Take the semaphore to prevent writes
// to the palette/screen before we're done
_renderFinished = false; // set to synchronize with callback thread
#endif
#ifdef ENABLE_RENDER_MEASURE
_lastRenderTime = g_system->getMillis();
#endif /* ENABLE_RENDER_MEASURE */
sceGuStart(0, _displayList);
sceGuClearColor(0xFF000000);
sceGuClear(GU_COLOR_BUFFER_BIT);
sceGuAmbientColor(0xFFFFFFFF);
sceGuColor(0xFFFFFFFF);
sceGuTexOffset(0, 0);
sceGuTexFilter(GU_LINEAR, GU_LINEAR);
sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGBA); // Also good enough for all purposes
sceGuAlphaFunc(GU_GREATER, 0, 0xFF); // Also good enough for all purposes
}
inline void MasterGuRenderer::guPostRender() {
DEBUG_ENTER_FUNC();
sceGuFinish();
#ifdef USE_DISPLAY_CALLBACK
if (_callbackId < 0)
PSP_ERROR("bad callbackId[%d]\n", _callbackId);
else
sceKernelNotifyCallback(_callbackId, 0); // notify the callback. Nothing extra to pass
#else
sceGuSync(0, 0);
#ifdef ENABLE_RENDER_MEASURE
uint32 now = g_system->getMillis();
PSP_INFO_PRINT("Render took %d milliseconds\n", now - _lastRenderTime);
#endif /* ENABLE_RENDER_MEASURE */
sceDisplayWaitVblankStart();
sceGuSwapBuffers();
_renderFinished = true;
#endif /* !USE_DISPLAY_CALLBACK */
}
void MasterGuRenderer::guShutDown() {
sceGuTerm();
}
// Class DisplayManager -----------------------------------------------------
DisplayManager::~DisplayManager() {
_masterGuRenderer.guShutDown();
}
void DisplayManager::init() {
DEBUG_ENTER_FUNC();
_displayParams.outputBitsPerPixel = 32; // can be changed to produce 16-bit output
GuRenderer::setDisplayManager(this);
_screen->init();
_overlay->init();
_cursor->init();
_masterGuRenderer.guInit(); // start up the renderer
#ifdef USE_DISPLAY_CALLBACK
_masterGuRenderer.setupCallbackThread();
#endif
// Init overlay since we never change the size
_overlay->deallocate();
_overlay->setBytesPerPixel(sizeof(uint16));
_overlay->setSize(PSP_SCREEN_WIDTH, PSP_SCREEN_HEIGHT);
_overlay->allocate();
}
void DisplayManager::setSizeAndPixelFormat(uint width, uint height, const Graphics::PixelFormat *format) {
DEBUG_ENTER_FUNC();
PSP_DEBUG_PRINT("w[%u], h[%u], pformat[%p]\n", width, height, format);
_screen->deallocate();
_screen->setScummvmPixelFormat(format);
_screen->setSize(width, height);
_screen->allocate();
_cursor->setScreenPaletteScummvmPixelFormat(format);
_displayParams.screenSource.width = width;
_displayParams.screenSource.height = height;
calculateScaleParams();
}
bool DisplayManager::setGraphicsMode(int mode) {
DEBUG_ENTER_FUNC();
_graphicsMode = mode;
calculateScaleParams();
return true;
}
void DisplayManager::calculateScaleParams() {
if (!_displayParams.screenSource.width || !_displayParams.screenSource.height)
return; // we can't calculate anything without these
switch (_graphicsMode) {
case ORIGINAL_RESOLUTION:
// check if we can fit the original resolution inside the screen
if ((_displayParams.screenSource.width <= PSP_SCREEN_WIDTH) &&
(_displayParams.screenSource.height <= PSP_SCREEN_HEIGHT)) {
_displayParams.screenOutput.width = _displayParams.screenSource.width;
_displayParams.screenOutput.height = _displayParams.screenSource.height;
break;
}
// else revert to fit to screen
case FIT_TO_SCREEN: { // maximize the height while keeping aspect ratio
float aspectRatio;
if (ConfMan.getBool("aspect_ratio")) {
aspectRatio = 4.0f / 3.0f;
} else {
aspectRatio = (float)_displayParams.screenSource.width / (float)_displayParams.screenSource.height;
}
_displayParams.screenOutput.height = PSP_SCREEN_HEIGHT; // always full height
_displayParams.screenOutput.width = (uint32)(PSP_SCREEN_HEIGHT * aspectRatio);
if (_displayParams.screenOutput.width > PSP_SCREEN_WIDTH) { // shrink if wider than screen
_displayParams.screenOutput.height = (uint32)(((float)PSP_SCREEN_HEIGHT * (float)PSP_SCREEN_WIDTH) / (float)_displayParams.screenOutput.width);
_displayParams.screenOutput.width = PSP_SCREEN_WIDTH;
}
}
break;
case STRETCH_TO_SCREEN: // we simply stretch to the whole screen
_displayParams.screenOutput.width = PSP_SCREEN_WIDTH;
_displayParams.screenOutput.height = PSP_SCREEN_HEIGHT;
break;
default:
PSP_ERROR("Unsupported graphics mode[%d].\n", _graphicsMode);
}
// calculate scale factors for X and Y
_displayParams.scaleX = ((float)_displayParams.screenOutput.width) / _displayParams.screenSource.width;
_displayParams.scaleY = ((float)_displayParams.screenOutput.height) / _displayParams.screenSource.height;
}
void DisplayManager::waitUntilRenderFinished() {
#ifdef USE_DISPLAY_CALLBACK
_masterGuRenderer.sleepUntilRenderFinished();
#endif /* USE_DISPLAY_CALLBACK */
}
// return true if we really rendered or no dirty. False otherwise
bool DisplayManager::renderAll() {
DEBUG_ENTER_FUNC();
#ifdef USE_DISPLAY_CALLBACK
if (!_masterGuRenderer.isRenderFinished()) {
PSP_DEBUG_PRINT("Callback render not finished.\n");
return false; // didn't render
}
#endif /* USE_DISPLAY_CALLBACK */
// This is cheaper than checking time, so we do it first
// Any one of these being dirty causes everything to draw
if (!_screen->isDirty() &&
!_overlay->isDirty() &&
!_cursor->isDirty() &&
!_keyboard->isDirty() &&
!_imageViewer->isDirty()) {
PSP_DEBUG_PRINT("Nothing dirty\n");
return true; // nothing to render
}
if (!isTimeToUpdate())
return false; // didn't render
PSP_DEBUG_PRINT("dirty: screen[%s], overlay[%s], cursor[%s], keyboard[%s], imageViewer[%s]\n",
_screen->isDirty() ? "true" : "false",
_overlay->isDirty() ? "true" : "false",
_cursor->isDirty() ? "true" : "false",
_keyboard->isDirty() ? "true" : "false",
_imageViewer->isDirty() ? "true" : "false"
);
_masterGuRenderer.guPreRender(); // Set up rendering
_screen->render();
_screen->setClean(); // clean out dirty bit
if (_imageViewer->isVisible())
_imageViewer->render();
_imageViewer->setClean();
if (_overlay->isVisible())
_overlay->render();
_overlay->setClean();
if (_cursor->isVisible())
_cursor->render();
_cursor->setClean();
if (_keyboard->isVisible())
_keyboard->render();
_keyboard->setClean();
_masterGuRenderer.guPostRender();
return true; // rendered successfully
}
inline bool DisplayManager::isTimeToUpdate() {
#define MAX_FPS 60 // was 30
uint32 now = g_system->getMillis();
if (now - _lastUpdateTime < (1000 / MAX_FPS))
return false;
_lastUpdateTime = now;
return true;
}
Common::List<Graphics::PixelFormat> DisplayManager::getSupportedPixelFormats() const {
Common::List<Graphics::PixelFormat> list;
// In order of preference
list.push_back(PSPPixelFormat::convertToScummvmPixelFormat(PSPPixelFormat::Type_5650));
list.push_back(PSPPixelFormat::convertToScummvmPixelFormat(PSPPixelFormat::Type_5551));
list.push_back(PSPPixelFormat::convertToScummvmPixelFormat(PSPPixelFormat::Type_4444));
list.push_back(Graphics::PixelFormat::createFormatCLUT8());
return list;
}