/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2014 - Daniel De Matteis * * RetroArch 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 Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch 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 RetroArch. * If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Display dimensions #define DISPLAY_WIDTH 960 #define DISPLAY_HEIGHT 544 #define DISPLAY_STRIDE_IN_PIXELS 1024 #define DISPLAY_PIXEL_FORMAT SCE_DISPLAY_PIXELFORMAT_A8B8G8R8 #define DISPLAY_BUFFER_COUNT 3 #define DISPLAY_MAX_PENDING_SWAPS 2 // Supported flip modes enum FlipMode { FLIP_MODE_HSYNC, ///< Flip on next HSYNC FLIP_MODE_VSYNC, ///< Flip on next VSYNC FLIP_MODE_VSYNC_2 ///< Flip on next VSYNC, display for 2 VSYNCs minimum }; #define ALIGN(x, a) (((x) + ((a) - 1)) & ~((a) - 1)) #define MAX(a, b) (((a) > (b)) ? (a) : (b)) // Data structure to pass through the display queue typedef struct DisplayData { void *address; ///< Framebuffer address uint32_t width; ///< Framebuffer width uint32_t height; ///< Framebuffer height uint32_t strideInPixels; ///< Framebuffer stride in pixels uint32_t flipMode; ///< From #FlipMode } DisplayData; typedef struct psp2_video { SceGxmRenderTarget *g_mainRenderTarget; SceGxmContext *g_context; const SceGxmProgramParameter *g_clearColorParam; SceGxmShaderPatcher *g_shaderPatcher; SceGxmColorSurface g_displaySurface[DISPLAY_BUFFER_COUNT]; SceGxmSyncObject *g_displayBufferSync[DISPLAY_BUFFER_COUNT]; SceUID g_displayBufferUid[DISPLAY_BUFFER_COUNT]; void *g_displayBufferData[DISPLAY_BUFFER_COUNT]; void *g_initializeHostMem; bool smooth; int rotation; bool vsync; bool rgb32; unsigned width, height; } psp2_video_t; uint32_t g_displayFrontBufferIndex = DISPLAY_BUFFER_COUNT - 1; // initialization parameters SceUID g_initializeDriverUid = 0; SceUID g_initializeParameterBufferUid = 0; // libgxm context void *g_contextHostMem = NULL; SceUID g_vdmRingBufferUid = 0; SceUID g_vertexRingBufferUid = 0; SceUID g_fragmentRingBufferUid = 0; SceUID g_fragmentUsseRingBufferUid = 0; // libgxm shader patcher SceUID g_patcherBufferUid = 0; SceUID g_patcherCombinedUsseUid = 0; // libgxm display queue uint32_t g_displayBackBufferIndex = 0; // Depth buffer for display surface SceUID g_mainDepthBufferUid = 0; SceGxmDepthStencilSurface g_mainDepthSurface; static void *patcherHostAlloc(void *userData, uint32_t size) { (void)userData; return malloc(size); } // Callback function to allocate memory for the shader patcher static void patcherHostFree(void *userData, void *mem) { (void)userData; free(mem); } static void displayCallback(const void *callbackData) { int err = SCE_OK; (void)err; // Cast the parameters back const DisplayData *displayData = (const DisplayData *)callbackData; // Check this buffer has been displayed for the necessary number of VSYNCs // (Avoids queuing a flip before the second VSYNC has happened) if (displayData->flipMode == FLIP_MODE_VSYNC_2) err = sceDisplayWaitSetFrameBufMulti(2); // Swap to the new buffer SceDisplayFrameBuf framebuf; memset(&framebuf, 0x00, sizeof(SceDisplayFrameBuf)); framebuf.size = sizeof(SceDisplayFrameBuf); framebuf.base = displayData->address; framebuf.pitch = displayData->strideInPixels; framebuf.pixelformat = DISPLAY_PIXEL_FORMAT; framebuf.width = displayData->width; framebuf.height = displayData->height; err = sceDisplaySetFrameBuf(&framebuf, (displayData->flipMode == FLIP_MODE_HSYNC) ? SCE_DISPLAY_UPDATETIMING_NEXTHSYNC : SCE_DISPLAY_UPDATETIMING_NEXTVSYNC); assert(err == SCE_OK); // Block this callback until the swap has occurred and the old buffer // is no longer displayed if (displayData->flipMode != FLIP_MODE_HSYNC) { err = sceDisplayWaitSetFrameBuf(); assert(err == SCE_OK); } } static void createGxmShaderPatcher(void *data) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; // set buffer sizes for this sample const uint32_t patcherBufferSize = 64*1024; const uint32_t patcherCombinedUsseSize = 64*1024; // allocate memory for buffers and USSE code void *patcherBuffer = gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, patcherBufferSize, 4, SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE, &g_patcherBufferUid); uint32_t patcherVertexUsseOffset; uint32_t patcherFragmentUsseOffset; void *patcherCombinedUsse = combinedUsseAlloc( patcherCombinedUsseSize, &g_patcherCombinedUsseUid, &patcherVertexUsseOffset, &patcherFragmentUsseOffset); // create a shader patcher SceGxmShaderPatcherParams patcherParams; memset(&patcherParams, 0, sizeof(SceGxmShaderPatcherParams)); patcherParams.userData = NULL; patcherParams.hostAllocCallback = &patcherHostAlloc; patcherParams.hostFreeCallback = &patcherHostFree; patcherParams.bufferAllocCallback = NULL; patcherParams.bufferFreeCallback = NULL; patcherParams.bufferMem = patcherBuffer; patcherParams.bufferMemSize = patcherBufferSize; patcherParams.vertexUsseAllocCallback = NULL; patcherParams.vertexUsseFreeCallback = NULL; patcherParams.vertexUsseMem = patcherCombinedUsse; patcherParams.vertexUsseMemSize = patcherCombinedUsseSize; patcherParams.vertexUsseOffset = patcherVertexUsseOffset; patcherParams.fragmentUsseAllocCallback = NULL; patcherParams.fragmentUsseFreeCallback = NULL; patcherParams.fragmentUsseMem = patcherCombinedUsse; patcherParams.fragmentUsseMemSize = patcherCombinedUsseSize; patcherParams.fragmentUsseOffset = patcherFragmentUsseOffset; err = sceGxmShaderPatcherCreate(&patcherParams, &psp->g_shaderPatcher); assert(err == SCE_OK); } // Queue a display swap and cycle our buffers static int cycleDisplayBuffers(void *data, FlipMode flipMode, uint32_t width, uint32_t height, uint32_t strideInPixels) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; // queue the display swap for this frame DisplayData displayData; displayData.address = psp->g_displayBufferData[g_displayBackBufferIndex]; displayData.width = width; displayData.height = height; displayData.strideInPixels = strideInPixels; displayData.flipMode = flipMode; err = sceGxmDisplayQueueAddEntry( psp->g_displayBufferSync[psp->g_displayFrontBufferIndex], // front buffer is OLD buffer psp->g_displayBufferSync[g_displayBackBufferIndex], // back buffer is NEW buffer &displayData); assert(err == SCE_OK); // update buffer indices psp->g_displayFrontBufferIndex = g_displayBackBufferIndex; g_displayBackBufferIndex = (g_displayBackBufferIndex + 1) % DISPLAY_BUFFER_COUNT; // done return err; } static SceGxmRenderTarget *createRenderTarget(uint32_t width, uint32_t height, SceGxmMultisampleMode msaaMode) { int err = SCE_OK; (void)err; // set up parameters SceGxmRenderTargetParams params; memset(¶ms, 0, sizeof(SceGxmRenderTargetParams)); params.flags = 0; params.width = width; params.height = height; params.scenesPerFrame = 1; params.multisampleMode = msaaMode; params.multisampleLocations = 0; params.hostMem = NULL; params.hostMemSize = 0; params.driverMemBlock = -1; // compute sizes uint32_t hostMemSize, driverMemSize; err = sceGxmGetRenderTargetMemSizes(¶ms, &hostMemSize, &driverMemSize); assert(err == SCE_OK); // allocate host memory params.hostMem = malloc(hostMemSize); params.hostMemSize = hostMemSize; // allocate driver memory params.driverMemBlock = sceKernelAllocMemBlock( "SampleRT", SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, driverMemSize, NULL); assert(params.driverMemBlock >= SCE_OK); // create the render target SceGxmRenderTarget *renderTarget; err = sceGxmCreateRenderTarget(¶ms, &renderTarget); assert(err == SCE_OK); return renderTarget; } static void destroyRenderTarget(void *data, SceGxmRenderTarget *renderTarget) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; // grab the host memory and driver memblock void *hostMem; err = sceGxmRenderTargetGetHostMem(renderTarget, &hostMem); assert(err == SCE_OK); SceUID driverMemBlock; err = sceGxmRenderTargetGetDriverMemBlock(renderTarget, &driverMemBlock); assert(err == SCE_OK); // destroy the render target err = sceGxmDestroyRenderTarget(renderTarget); assert(err == SCE_OK); // free memory sceKernelFreeMemBlock(driverMemBlock); free(hostMem); } static void *gmmAlloc(SceKernelMemBlockType type, uint32_t size, uint32_t alignment, uint32_t attribs, SceUID *uid) { int err = SCE_OK; (void)err; // page align the size if (type == SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RWDATA) { // CDRAM memblocks must be 256KiB aligned assert(alignment <= 256*1024); size = ALIGN(size, 256*1024); } else { // LPDDR memblocks must be 4KiB aligned assert(alignment <= 4*1024); size = ALIGN(size, 4*1024); } (void)alignment; // allocate some memory *uid = sceKernelAllocMemBlock("common", type, size, NULL); assert(*uid >= SCE_OK); // grab the base address void *mem = NULL; err = sceKernelGetMemBlockBase(*uid, &mem); assert(err == SCE_OK); // map for the GPU err = sceGxmMapMemory(mem, size, attribs); assert(err == SCE_OK); // done return mem; } static void gmmFree(SceUID uid) { int err = SCE_OK; (void)err; // grab the base address void *mem = NULL; err = sceKernelGetMemBlockBase(uid, &mem); assert(err == SCE_OK); // unmap memory err = sceGxmUnmapMemory(mem); assert(err == SCE_OK); // free the memory block err = sceKernelFreeMemBlock(uid); assert(err == SCE_OK); } static void *vertexUsseAlloc(uint32_t size, SceUID *uid, uint32_t *usseOffset) { int err = SCE_OK; (void)err; // align to memblock alignment for LPDDR size = ALIGN(size, 4096); // allocate some memory *uid = sceKernelAllocMemBlock("basic", SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, size, NULL); assert(*uid >= SCE_OK); // grab the base address void *mem = NULL; err = sceKernelGetMemBlockBase(*uid, &mem); assert(err == SCE_OK); // map as vertex USSE code for the GPU err = sceGxmMapVertexUsseMemory(mem, size, usseOffset); assert(err == SCE_OK); // done return mem; } static void vertexUsseFree(SceUID uid) { int err = SCE_OK; (void)err; // grab the base address void *mem = NULL; err = sceKernelGetMemBlockBase(uid, &mem); assert(err == SCE_OK); // unmap memory err = sceGxmUnmapVertexUsseMemory(mem); assert(err == SCE_OK); // free the memory block err = sceKernelFreeMemBlock(uid); assert(err == SCE_OK); } static void *fragmentUsseAlloc(uint32_t size, SceUID *uid, uint32_t *usseOffset) { int err = SCE_OK; (void)err; // align to memblock alignment for LPDDR size = ALIGN(size, 4096); // allocate some memory *uid = sceKernelAllocMemBlock("basic", SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, size, NULL); assert(*uid >= SCE_OK); // grab the base address void *mem = NULL; err = sceKernelGetMemBlockBase(*uid, &mem); assert(err == SCE_OK); // map as fragment USSE code for the GPU err = sceGxmMapFragmentUsseMemory(mem, size, usseOffset); assert(err == SCE_OK); // done return mem; } static void fragmentUsseFree(SceUID uid) { int err = SCE_OK; (void)err; // grab the base address void *mem = NULL; err = sceKernelGetMemBlockBase(uid, &mem); assert(err == SCE_OK); // unmap memory err = sceGxmUnmapFragmentUsseMemory(mem); assert(err == SCE_OK); // free the memory block err = sceKernelFreeMemBlock(uid); assert(err == SCE_OK); } static void *combinedUsseAlloc(uint32_t size, SceUID *uid, uint32_t *vertexUsseOffset, uint32_t *fragmentUsseOffset) { int err = SCE_OK; (void)err; // align to memblock alignment for LPDDR size = ALIGN(size, 4096); // allocate some memory *uid = sceKernelAllocMemBlock("basic", SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, size, NULL); assert(*uid >= SCE_OK); // grab the base address void *mem = NULL; err = sceKernelGetMemBlockBase(*uid, &mem); assert(err == SCE_OK); // map as both vertex and fragment USSE for code the GPU err = sceGxmMapVertexUsseMemory(mem, size, vertexUsseOffset); assert(err == SCE_OK); err = sceGxmMapFragmentUsseMemory(mem, size, fragmentUsseOffset); assert(err == SCE_OK); // done return mem; } void combinedUsseFree(SceUID uid) { int err = SCE_OK; (void)err; // grab the base address void *mem = NULL; err = sceKernelGetMemBlockBase(uid, &mem); assert(err == SCE_OK); // unmap memory err = sceGxmUnmapFragmentUsseMemory(mem); assert(err == SCE_OK); err = sceGxmUnmapVertexUsseMemory(mem); assert(err == SCE_OK); // free the memory block err = sceKernelFreeMemBlock(uid); assert(err == SCE_OK); } using namespace sce::Vectormath::Scalar::Aos; // Embedded GXM shader programs extern const SceGxmProgram _binary_clear_v_gxp_start; extern const SceGxmProgram _binary_clear_f_gxp_start;; extern const SceGxmProgram _binary_cube_v_gxp_start; extern const SceGxmProgram _binary_cube_f_gxp_start; extern const uint8_t _binary_test_gxt_start[]; // Data structure for clear geometry typedef struct ClearVertex { float x; float y; } ClearVertex; // Data structure for basic geometry typedef struct BasicVertex { float x; float y; float z; uint32_t color; uint16_t u; uint16_t v; } BasicVertex; // clear geometry data SceGxmShaderPatcherId g_clearVertexProgramId; SceGxmShaderPatcherId g_clearFragmentProgramId; SceGxmVertexProgram *g_clearVertexProgram = NULL; SceGxmFragmentProgram *g_clearFragmentProgram = NULL; SceUID g_clearVerticesUid; SceUID g_clearIndicesUid; ClearVertex *g_clearVertices = NULL; uint16_t *g_clearIndices = NULL; // cube geometry data SceGxmShaderPatcherId g_cubeVertexProgramId; SceGxmShaderPatcherId g_cubeFragmentProgramId; SceGxmVertexProgram *g_cubeVertexProgram = NULL; SceGxmFragmentProgram *g_cubeFragmentProgram = NULL; SceUID g_cubeVerticesUid; SceUID g_cubeIndicesUid; BasicVertex *g_cubeVertices = NULL; uint16_t *g_cubeIndices = NULL; const SceGxmProgramParameter *g_cubeWvpParam = NULL; // offscreen surface data and render target SceUID g_offscreenColorBufferUid; void *g_offscreenColorBufferData; SceGxmColorSurface g_offscreenColorSurface; SceGxmTexture g_offscreenTexture; SceUID g_offscreenDepthBufferUid; void *g_offscreenDepthBufferData; SceGxmDepthStencilSurface g_offscreenDepthSurface; SceGxmRenderTarget *g_offscreenRenderTarget; // test texture SceUID g_testTextureDataUid; uint8_t *g_testTextureData; SceGxmTexture g_testTexture; // update data float g_rotationAngle = 0.0f; Matrix4 g_offscreenWvpMatrix; Matrix4 g_mainWvpMatrix; // Create data for clear draw call static void createClearData(void *data) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; // register programs with the shader patcher err = sceGxmShaderPatcherRegisterProgram(psp->g_shaderPatcher, &_binary_clear_v_gxp_start, &g_clearVertexProgramId); assert(err == SCE_OK); err = sceGxmShaderPatcherRegisterProgram(psp->g_shaderPatcher, &_binary_clear_f_gxp_start, &g_clearFragmentProgramId); assert(err == SCE_OK); // find attributes by name to create vertex format bindings const SceGxmProgram *clearVertexProgram = sceGxmShaderPatcherGetProgramFromId(g_clearVertexProgramId); const SceGxmProgramParameter *paramPositionAttribute = sceGxmProgramFindParameterByName(clearVertexProgram, "aPosition"); assert(paramPositionAttribute && (sceGxmProgramParameterGetCategory(paramPositionAttribute) == SCE_GXM_PARAMETER_CATEGORY_ATTRIBUTE)); // find fragment uniforms by name and cache parameter info // note: name lookup is a slow load-time operation const SceGxmProgram *clearFragmentProgram = sceGxmShaderPatcherGetProgramFromId(g_clearFragmentProgramId); assert(clearFragmentProgram); psp->g_clearColorParam = sceGxmProgramFindParameterByName(clearFragmentProgram, "color"); assert(psp->g_clearColorParam && (sceGxmProgramParameterGetCategory(psp->g_clearColorParam) == SCE_GXM_PARAMETER_CATEGORY_UNIFORM)); // create clear vertex format SceGxmVertexAttribute clearVertexAttributes[1]; SceGxmVertexStream clearVertexStreams[1]; clearVertexAttributes[0].streamIndex = 0; clearVertexAttributes[0].offset = 0; clearVertexAttributes[0].format = SCE_GXM_ATTRIBUTE_FORMAT_F32; clearVertexAttributes[0].componentCount = 2; clearVertexAttributes[0].regIndex = sceGxmProgramParameterGetResourceIndex(paramPositionAttribute); clearVertexStreams[0].stride = sizeof(ClearVertex); clearVertexStreams[0].indexSource = SCE_GXM_INDEX_SOURCE_INDEX_16BIT; // create clear programs err = sceGxmShaderPatcherCreateVertexProgram( psp->g_shaderPatcher, g_clearVertexProgramId, clearVertexAttributes, 1, clearVertexStreams, 1, &g_clearVertexProgram); assert(err == SCE_OK); err = sceGxmShaderPatcherCreateFragmentProgram( psp->g_shaderPatcher, g_clearFragmentProgramId, SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4, SCE_GXM_MULTISAMPLE_NONE, NULL, sceGxmShaderPatcherGetProgramFromId(g_clearVertexProgramId), &g_clearFragmentProgram); assert(err == SCE_OK); // allocate vertices and indices g_clearVertices = (ClearVertex *)gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, 3*sizeof(ClearVertex), 4, SCE_GXM_MEMORY_ATTRIB_READ, &g_clearVerticesUid); g_clearIndices = (uint16_t *)gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, 3*sizeof(uint16_t), 2, SCE_GXM_MEMORY_ATTRIB_READ, &g_clearIndicesUid); // write vertex data g_clearVertices[0].x = -1.0f; g_clearVertices[0].y = -1.0f; g_clearVertices[1].x = 3.0f; g_clearVertices[1].y = -1.0f; g_clearVertices[2].x = -1.0f; g_clearVertices[2].y = 3.0f; // write index data g_clearIndices[0] = 0; g_clearIndices[1] = 1; g_clearIndices[2] = 2; } static void destroyClearData(void *data) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; // release the shaderss err = sceGxmShaderPatcherReleaseFragmentProgram(psp->g_shaderPatcher, g_clearFragmentProgram); assert(err == SCE_OK); err = sceGxmShaderPatcherReleaseVertexProgram(psp->g_shaderPatcher, g_clearVertexProgram); assert(err == SCE_OK); // free the memory used for vertices and indices gmmFree(g_clearIndicesUid); gmmFree(g_clearVerticesUid); // unregister programs since we don't need them any more err = sceGxmShaderPatcherUnregisterProgram(psp->g_shaderPatcher, g_clearFragmentProgramId); assert(err == SCE_OK); err = sceGxmShaderPatcherUnregisterProgram(psp->g_shaderPatcher, g_clearVertexProgramId); assert(err == SCE_OK); } // Create data for cube draw call static void createCubeData(void *data) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; // register programs with the patcher err = sceGxmShaderPatcherRegisterProgram(psp->g_shaderPatcher, &_binary_cube_v_gxp_start, &g_cubeVertexProgramId); assert(err == SCE_OK); err = sceGxmShaderPatcherRegisterProgram(psp->g_shaderPatcher, &_binary_cube_f_gxp_start, &g_cubeFragmentProgramId); assert(err == SCE_OK); // find vertex uniforms by name and cache parameter info // note: name lookup is a slow load-time operation const SceGxmProgram *cubeVertexProgram = sceGxmShaderPatcherGetProgramFromId(g_cubeVertexProgramId); assert(cubeVertexProgram); g_cubeWvpParam = sceGxmProgramFindParameterByName(cubeVertexProgram, "wvp"); assert(g_cubeWvpParam && (sceGxmProgramParameterGetCategory(g_cubeWvpParam) == SCE_GXM_PARAMETER_CATEGORY_UNIFORM)); // find attributes by name to create vertex format bindings const SceGxmProgramParameter *paramPositionAttribute = sceGxmProgramFindParameterByName(cubeVertexProgram, "aPosition"); assert(paramPositionAttribute && (sceGxmProgramParameterGetCategory(paramPositionAttribute) == SCE_GXM_PARAMETER_CATEGORY_ATTRIBUTE)); const SceGxmProgramParameter *paramColorAttribute = sceGxmProgramFindParameterByName(cubeVertexProgram, "aColor"); assert(paramColorAttribute && (sceGxmProgramParameterGetCategory(paramColorAttribute) == SCE_GXM_PARAMETER_CATEGORY_ATTRIBUTE)); const SceGxmProgramParameter *paramTexCoordAttribute = sceGxmProgramFindParameterByName(cubeVertexProgram, "aTexCoord"); assert(paramTexCoordAttribute && (sceGxmProgramParameterGetCategory(paramTexCoordAttribute) == SCE_GXM_PARAMETER_CATEGORY_ATTRIBUTE)); // create shaded triangle vertex format SceGxmVertexAttribute basicVertexAttributes[3]; SceGxmVertexStream basicVertexStreams[1]; basicVertexAttributes[0].streamIndex = 0; basicVertexAttributes[0].offset = 0; basicVertexAttributes[0].format = SCE_GXM_ATTRIBUTE_FORMAT_F32; basicVertexAttributes[0].componentCount = 3; basicVertexAttributes[0].regIndex = sceGxmProgramParameterGetResourceIndex(paramPositionAttribute); basicVertexAttributes[1].streamIndex = 0; basicVertexAttributes[1].offset = 12; basicVertexAttributes[1].format = SCE_GXM_ATTRIBUTE_FORMAT_U8N; basicVertexAttributes[1].componentCount = 4; basicVertexAttributes[1].regIndex = sceGxmProgramParameterGetResourceIndex(paramColorAttribute); basicVertexAttributes[2].streamIndex = 0; basicVertexAttributes[2].offset = 16; basicVertexAttributes[2].format = SCE_GXM_ATTRIBUTE_FORMAT_F16; basicVertexAttributes[2].componentCount = 2; basicVertexAttributes[2].regIndex = sceGxmProgramParameterGetResourceIndex(paramTexCoordAttribute); basicVertexStreams[0].stride = sizeof(BasicVertex); basicVertexStreams[0].indexSource = SCE_GXM_INDEX_SOURCE_INDEX_16BIT; // create cube vertex program err = sceGxmShaderPatcherCreateVertexProgram( psp->g_shaderPatcher, g_cubeVertexProgramId, basicVertexAttributes, 3, basicVertexStreams, 1, &g_cubeVertexProgram); assert(err == SCE_OK); // create cube fragment program err = sceGxmShaderPatcherCreateFragmentProgram( psp->g_shaderPatcher, g_cubeFragmentProgramId, SCE_GXM_OUTPUT_REGISTER_FORMAT_UCHAR4, SCE_GXM_MULTISAMPLE_NONE, NULL, sceGxmShaderPatcherGetProgramFromId(g_cubeVertexProgramId), &g_cubeFragmentProgram); assert(err == SCE_OK); // allocate memory for vertex and index data g_cubeVertices = (BasicVertex *)gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, 24*sizeof(BasicVertex), 4, SCE_GXM_MEMORY_ATTRIB_READ, &g_cubeVerticesUid); g_cubeIndices = (uint16_t *)gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, 36*sizeof(uint16_t), 2, SCE_GXM_MEMORY_ATTRIB_READ, &g_cubeIndicesUid); // write vertices BasicVertex *vertexData = g_cubeVertices; for (uint32_t face = 0; face < 6; ++face) { float sign = ((face & 0x1) ? 1.0f : -1.0f); uint32_t axis = face >> 1; float ox = (axis == 0) ? sign : 0.0f; float oy = (axis == 1) ? sign : 0.0f; float oz = (axis == 2) ? sign : 0.0f; float ux = (axis != 0) ? 1.0f : 0.0f; float uy = (axis == 0) ? 1.0f : 0.0f; float uz = 0.0f; float vx = 0.0f; float vy = (axis == 2) ? 1.0f : 0.0f; float vz = (axis != 2) ? 1.0f : 0.0f; uint16_t half0 = 0x0000; uint16_t half1 = 0x3c00; uint32_t color = 0; switch (axis) { case 0: color = 0xffffffff; break; case 1: color = 0xffdddddd; break; case 2: color = 0xffbbbbbb; break; } vertexData->x = ox - ux - vx; vertexData->y = oy - uy - vy; vertexData->z = oz - uz - vz; vertexData->color = color; vertexData->u = half0; vertexData->v = half1; ++vertexData; vertexData->x = ox + ux - vx; vertexData->y = oy + uy - vy; vertexData->z = oz + uz - vz; vertexData->color = color; vertexData->u = half1; vertexData->v = half1; ++vertexData; vertexData->x = ox + ux + vx; vertexData->y = oy + uy + vy; vertexData->z = oz + uz + vz; vertexData->color = color; vertexData->u = half1; vertexData->v = half0; ++vertexData; vertexData->x = ox - ux + vx; vertexData->y = oy - uy + vy; vertexData->z = oz - uz + vz; vertexData->color = color; vertexData->u = half0; vertexData->v = half0; ++vertexData; } // write indices uint16_t *indexData = g_cubeIndices; for (uint32_t face = 0; face < 6; ++face) { uint32_t offset = 4*face; *indexData++ = offset + 0; *indexData++ = offset + 1; *indexData++ = offset + 2; *indexData++ = offset + 2; *indexData++ = offset + 3; *indexData++ = offset + 0; } } static void destroyCubeData(void *data) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; // release the shaderss err = sceGxmShaderPatcherReleaseFragmentProgram(psp->g_shaderPatcher, g_cubeFragmentProgram); assert(err == SCE_OK); err = sceGxmShaderPatcherReleaseVertexProgram(psp->g_shaderPatcher, g_cubeVertexProgram); assert(err == SCE_OK); // free the memory used for vertices and indices gmmFree(g_cubeIndicesUid); gmmFree(g_cubeVerticesUid); // unregister programs since we don't need them any more err = sceGxmShaderPatcherUnregisterProgram(psp->g_shaderPatcher, g_cubeFragmentProgramId); assert(err == SCE_OK); err = sceGxmShaderPatcherUnregisterProgram(psp->g_shaderPatcher, g_cubeVertexProgramId); assert(err == SCE_OK); } // Create test texture data static void createTestTextureData(void *data) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; // validate gxt const void *gxt = _binary_test_gxt_start; assert(sceGxtCheckData(gxt) == SCE_OK); // get the size of the texture data const uint32_t dataSize = sceGxtGetDataSize(gxt); // allocate memory g_testTextureData = (uint8_t *)gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, dataSize, SCE_GXM_TEXTURE_ALIGNMENT, SCE_GXM_MEMORY_ATTRIB_READ, &g_testTextureDataUid); // copy texture data const void *dataSrc = sceGxtGetDataAddress(gxt); memcpy(g_testTextureData, dataSrc, dataSize); // set up the texture control words err = sceGxtInitTexture(&g_testTexture, gxt, g_testTextureData, 0); assert(err == SCE_OK); // set linear filtering err = sceGxmTextureSetMagFilter( &g_testTexture, SCE_GXM_TEXTURE_FILTER_LINEAR); assert(err == SCE_OK); err = sceGxmTextureSetMinFilter( &g_testTexture, SCE_GXM_TEXTURE_FILTER_LINEAR); assert(err == SCE_OK); } static void destroyTestTextureData(void *data) { psp2_video_t *psp = (psp2_video_t*)data; gmmFree(g_testTextureDataUid); } static void createOffscreenBuffer(void *data, const video_info_t *video) { psp2_video_t *psp = (psp2_video_t*)data; int err = SCE_OK; (void)err; /* TODO - ensure width/height is POT - if libgxm cares about that */ // allocate memory g_offscreenColorBufferData = gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RWDATA, video->width * video->height * (video->rgb32 ? 4 : 2), MAX(SCE_GXM_TEXTURE_ALIGNMENT, SCE_GXM_COLOR_SURFACE_ALIGNMENT), SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE, &g_offscreenColorBufferUid); // set up the surface err = sceGxmColorSurfaceInit( &g_offscreenColorSurface, video->rgb32 ? SCE_GXM_COLOR_FORMAT_A8R8G8B8 : SCE_GXM_COLOR_FORMAT_R5G6B5, SCE_GXM_COLOR_SURFACE_LINEAR, SCE_GXM_COLOR_SURFACE_SCALE_NONE, SCE_GXM_OUTPUT_REGISTER_SIZE_32BIT, video->width, video->height, video->width, g_offscreenColorBufferData); assert(err == SCE_OK); // set up the texture err = sceGxmTextureInitLinear( &g_offscreenTexture, g_offscreenColorBufferData, video->rgb32 ? SCE_GXM_TEXTURE_FORMAT_A8B8G8R8 : SCE_GXM_TEXTURE_FORMAT_R5G6B5, video->width, video->height, 1); assert(err == SCE_OK); // set linear filtering err = sceGxmTextureSetMagFilter(&g_offscreenTexture, SCE_GXM_TEXTURE_FILTER_LINEAR); assert(err == SCE_OK); err = sceGxmTextureSetMinFilter(&g_offscreenTexture, SCE_GXM_TEXTURE_FILTER_LINEAR); assert(err == SCE_OK); // create the depth/stencil surface const uint32_t alignedWidth = ALIGN(DISPLAY_WIDTH, SCE_GXM_TILE_SIZEX); const uint32_t alignedHeight = ALIGN(DISPLAY_HEIGHT, SCE_GXM_TILE_SIZEY); uint32_t sampleCount = alignedWidth*alignedHeight; uint32_t depthStrideInSamples = alignedWidth; g_offscreenDepthBufferData = gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, 4*sampleCount, SCE_GXM_DEPTHSTENCIL_SURFACE_ALIGNMENT, SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE, &g_offscreenDepthBufferUid); err = sceGxmDepthStencilSurfaceInit( &g_offscreenDepthSurface, SCE_GXM_DEPTH_STENCIL_FORMAT_S8D24, SCE_GXM_DEPTH_STENCIL_SURFACE_TILED, depthStrideInSamples, g_offscreenDepthBufferData, NULL); // create a render target g_offscreenRenderTarget = createRenderTarget(video->width, video->height, SCE_GXM_MULTISAMPLE_NONE); } static void destroyOffscreenBuffer(void *data) { psp2_video_t *psp = (psp2_video_t*)data; // destroy render target destroyRenderTarget(psp, g_offscreenRenderTarget); // free the memory gmmFree(g_offscreenDepthBufferUid); gmmFree(g_offscreenColorBufferUid); } static void update(void *data) { psp2_video_t *psp = (psp2_video_t*)data; // advance rotation g_rotationAngle += 0.25f*SCE_MATH_TWOPI/60.0f; if (g_rotationAngle > SCE_MATH_TWOPI) g_rotationAngle -= SCE_MATH_TWOPI; // copmute our matrices Matrix4 offscreenProjectionMatrix = Matrix4::perspective( SCE_MATH_PI/4.0f, (float)psp->width /(float)psp->height, 0.1f, 10.0f); Matrix4 mainProjectionMatrix = Matrix4::perspective( SCE_MATH_PI/4.0f, (float)DISPLAY_WIDTH/(float)DISPLAY_HEIGHT, 0.1f, 10.0f); Matrix4 viewMatrix = Matrix4::translation(Vector3(0.0f, 0.0f, -5.0f)); Matrix4 worldMatrix = Matrix4::rotation(g_rotationAngle, Vector3(0.707f, 0.707f, 0.0f)); g_offscreenWvpMatrix = offscreenProjectionMatrix * viewMatrix * worldMatrix; g_mainWvpMatrix = mainProjectionMatrix * viewMatrix * worldMatrix; } static void renderOffscreen(void *data) { psp2_video_t *psp = (psp2_video_t*)data; // set up a scene, offscreen render target, no sync required sceGxmBeginScene( psp->g_context, 0, g_offscreenRenderTarget, NULL, NULL, NULL, &g_offscreenColorSurface, &g_offscreenDepthSurface); // set clear shaders sceGxmSetVertexProgram(psp->g_context, g_clearVertexProgram); sceGxmSetFragmentProgram(psp->g_context, g_clearFragmentProgram); // set the fragment program constants void *fragmentDefaultBuffer; sceGxmReserveFragmentDefaultUniformBuffer(psp->g_context, &fragmentDefaultBuffer); float clearColor[4] = { 1.0f, 1.0f, 0.2f, 0.0f }; sceGxmSetUniformDataF(fragmentDefaultBuffer, psp->g_clearColorParam, 0, 4, clearColor); // draw geometry sceGxmSetVertexStream(psp->g_context, 0, g_clearVertices); sceGxmDraw(psp->g_context, SCE_GXM_PRIMITIVE_TRIANGLES, SCE_GXM_INDEX_FORMAT_U16, g_clearIndices, 3); // render the cube sceGxmSetVertexProgram(psp->g_context, g_cubeVertexProgram); sceGxmSetFragmentProgram(psp->g_context, g_cubeFragmentProgram); sceGxmSetVertexStream(psp->g_context, 0, g_cubeVertices); sceGxmSetFragmentTexture(psp->g_context, 0, &g_testTexture); // set the vertex program constants void *vertexDefaultBuffer; sceGxmReserveVertexDefaultUniformBuffer(psp->g_context, &vertexDefaultBuffer); sceGxmSetUniformDataF(vertexDefaultBuffer, g_cubeWvpParam, 0, 16, (float *)&g_offscreenWvpMatrix); // draw the cube sceGxmDraw(psp->g_context, SCE_GXM_PRIMITIVE_TRIANGLES, SCE_GXM_INDEX_FORMAT_U16, g_cubeIndices, 36); // stop rendering to the offscreen render target sceGxmEndScene(psp->g_context, NULL, NULL); } static void renderMain(void *data) { psp2_video_t *psp = (psp2_video_t*)data; // set up a scene, main render target, synchronised with the back buffer sync sceGxmBeginScene( psp->g_context, 0, psp->g_mainRenderTarget, NULL, NULL, psp->g_displayBufferSync[g_displayBackBufferIndex], &psp->g_displaySurface[g_displayBackBufferIndex], &g_mainDepthSurface); // set clear shaders sceGxmSetVertexProgram(psp->g_context, g_clearVertexProgram); sceGxmSetFragmentProgram(psp->g_context, g_clearFragmentProgram); // set the fragment program constants void *fragmentDefaultBuffer; sceGxmReserveFragmentDefaultUniformBuffer(psp->g_context, &fragmentDefaultBuffer); float clearColor[4] = { 0.2f, 0.2f, 0.2f, 0.0f }; sceGxmSetUniformDataF(fragmentDefaultBuffer, psp->g_clearColorParam, 0, 4, clearColor); // draw geometry sceGxmSetVertexStream(psp->g_context, 0, g_clearVertices); sceGxmDraw(psp->g_context, SCE_GXM_PRIMITIVE_TRIANGLES, SCE_GXM_INDEX_FORMAT_U16, g_clearIndices, 3); // render the cube sceGxmSetVertexProgram(psp->g_context, g_cubeVertexProgram); sceGxmSetFragmentProgram(psp->g_context, g_cubeFragmentProgram); sceGxmSetVertexStream(psp->g_context, 0, g_cubeVertices); sceGxmSetFragmentTexture(psp->g_context, 0, &g_offscreenTexture); // set the vertex program constants void *vertexDefaultBuffer; sceGxmReserveVertexDefaultUniformBuffer(psp->g_context, &vertexDefaultBuffer); sceGxmSetUniformDataF(vertexDefaultBuffer, g_cubeWvpParam, 0, 16, (float *)&g_mainWvpMatrix); // draw the cube sceGxmDraw(psp->g_context, SCE_GXM_PRIMITIVE_TRIANGLES, SCE_GXM_INDEX_FORMAT_U16, g_cubeIndices, 36); // stop rendering to the main render target sceGxmEndScene(psp->g_context, NULL, NULL); // PA heartbeat to notify end of frame sceGxmPadHeartbeat( &psp->g_displaySurface[g_displayBackBufferIndex], psp->g_displayBufferSync[g_displayBackBufferIndex]); } static void *psp2_init(const video_info_t *video, const input_driver_t **input, void **input_data) { void *pspinput; psp2_video_t *psp = (psp2_video_t*)driver.video_data; if (!psp) { // first time init psp = (psp2_video_t*)calloc(1, sizeof(psp2_video_t)); if (!psp) goto error; } int err = SCE_OK; (void)err; // initialize libgxm // set up parameters SceGxmInitializeParams initializeParams; memset(&initializeParams, 0, sizeof(SceGxmInitializeParams)); initializeParams.flags = 0; initializeParams.displayQueueMaxPendingCount = DISPLAY_MAX_PENDING_SWAPS; initializeParams.displayQueueCallback = displayCallback; initializeParams.displayQueueCallbackDataSize = sizeof(DisplayData); initializeParams.parameterBufferSize = SCE_GXM_DEFAULT_PARAMETER_BUFFER_SIZE; // start libgxm err = sceGxmInitialize(&initializeParams); assert(err == SCE_OK); // create a rendering context // allocate host memory g_contextHostMem = malloc(SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE); // allocate ring buffer memory using default sizes void *vdmRingBuffer = gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE, 4, SCE_GXM_MEMORY_ATTRIB_READ, &g_vdmRingBufferUid); void *vertexRingBuffer = gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE, 4, SCE_GXM_MEMORY_ATTRIB_READ, &g_vertexRingBufferUid); void *fragmentRingBuffer = gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE, 4, SCE_GXM_MEMORY_ATTRIB_READ, &g_fragmentRingBufferUid); uint32_t fragmentUsseRingBufferOffset; void *fragmentUsseRingBuffer = fragmentUsseAlloc( SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE, &g_fragmentUsseRingBufferUid, &fragmentUsseRingBufferOffset); // set up parameters SceGxmContextParams contextParams; memset(&contextParams, 0, sizeof(SceGxmContextParams)); contextParams.hostMem = g_contextHostMem; contextParams.hostMemSize = SCE_GXM_MINIMUM_CONTEXT_HOST_MEM_SIZE; contextParams.vdmRingBufferMem = vdmRingBuffer; contextParams.vdmRingBufferMemSize = SCE_GXM_DEFAULT_VDM_RING_BUFFER_SIZE; contextParams.vertexRingBufferMem = vertexRingBuffer; contextParams.vertexRingBufferMemSize = SCE_GXM_DEFAULT_VERTEX_RING_BUFFER_SIZE; contextParams.fragmentRingBufferMem = fragmentRingBuffer; contextParams.fragmentRingBufferMemSize = SCE_GXM_DEFAULT_FRAGMENT_RING_BUFFER_SIZE; contextParams.fragmentUsseRingBufferMem = fragmentUsseRingBuffer; contextParams.fragmentUsseRingBufferMemSize = SCE_GXM_DEFAULT_FRAGMENT_USSE_RING_BUFFER_SIZE; contextParams.fragmentUsseRingBufferOffset = fragmentUsseRingBufferOffset; // create the context err = sceGxmCreateContext(&contextParams, &psp->g_context); assert(err == SCE_OK); // create a shader patcher createGxmShaderPatcher(psp); // allocate memory and sync objects for display buffers // FIXME: ensure physical contiguity for SceDisplay properly for (uint32_t i = 0; i < DISPLAY_BUFFER_COUNT; ++i) { // allocate memory with large (1MiB) size to ensure physical contiguity psp->g_displayBufferData[i] = gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RWDATA, ALIGN(4*DISPLAY_STRIDE_IN_PIXELS*DISPLAY_HEIGHT, 1*1024*1024), SCE_GXM_COLOR_SURFACE_ALIGNMENT, SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE, &psp->g_displayBufferUid[i]); // memset the buffer to a noticeable debug color for (uint32_t y = 0; y < DISPLAY_HEIGHT; ++y) { uint32_t *row = (uint32_t *)psp->g_displayBufferData[i] + y*DISPLAY_STRIDE_IN_PIXELS; for (uint32_t x = 0; x < DISPLAY_WIDTH; ++x) { row[x] = 0xffff00ff; } } // initialize a color surface for this display buffer err = sceGxmColorSurfaceInit( &psp->g_displaySurface[i], video->rgb32 ? SCE_GXM_COLOR_FORMAT_A8R8G8B8 : SCE_GXM_COLOR_FORMAT_R5G6B5, SCE_GXM_COLOR_SURFACE_LINEAR, SCE_GXM_COLOR_SURFACE_SCALE_NONE, SCE_GXM_OUTPUT_REGISTER_SIZE_32BIT, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_STRIDE_IN_PIXELS, psp->g_displayBufferData[i]); assert(err == SCE_OK); // create a sync object that we will associate with this buffer err = sceGxmSyncObjectCreate(&psp->g_displayBufferSync[i]); assert(err == SCE_OK); } // create a depth buffer const uint32_t alignedWidth = ALIGN(DISPLAY_WIDTH, SCE_GXM_TILE_SIZEX); const uint32_t alignedHeight = ALIGN(DISPLAY_HEIGHT, SCE_GXM_TILE_SIZEY); uint32_t sampleCount = alignedWidth*alignedHeight; uint32_t depthStrideInSamples = alignedWidth; void *mainDepthBufferData = gmmAlloc( SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA_UNCACHE, 4*sampleCount, SCE_GXM_DEPTHSTENCIL_SURFACE_ALIGNMENT, SCE_GXM_MEMORY_ATTRIB_READ | SCE_GXM_MEMORY_ATTRIB_WRITE, &g_mainDepthBufferUid); err = sceGxmDepthStencilSurfaceInit( &g_mainDepthSurface, SCE_GXM_DEPTH_STENCIL_FORMAT_S8D24, SCE_GXM_DEPTH_STENCIL_SURFACE_TILED, depthStrideInSamples, mainDepthBufferData, NULL); // swap to the current front buffer with VSYNC // (also ensures that future calls with HSYNC are successful) SceDisplayFrameBuf framebuf; memset(&framebuf, 0x00, sizeof(SceDisplayFrameBuf)); framebuf.size = sizeof(SceDisplayFrameBuf); framebuf.base = psp->g_displayBufferData[psp->g_displayFrontBufferIndex]; framebuf.pitch = DISPLAY_STRIDE_IN_PIXELS; framebuf.pixelformat = DISPLAY_PIXEL_FORMAT; framebuf.width = DISPLAY_WIDTH; framebuf.height = DISPLAY_HEIGHT; err = sceDisplaySetFrameBuf(&framebuf, SCE_DISPLAY_UPDATETIMING_NEXTVSYNC); assert(err == SCE_OK); err = sceDisplayWaitSetFrameBuf(); assert(err == SCE_OK); // create a render target that describes the tiling setup we want to use psp->g_mainRenderTarget = createRenderTarget(DISPLAY_WIDTH, DISPLAY_HEIGHT, SCE_GXM_MULTISAMPLE_NONE); // create graphics data createClearData(psp); createTestTextureData(psp); createCubeData(psp); psp->rgb32 = video->rgb32; psp->width = video->width; psp->width = video->height; createOffscreenBuffer(psp, video); if (input && input_data) { pspinput = input_psp.init(); *input = pspinput ? &input_psp : NULL; *input_data = pspinput; } return psp; error: RARCH_ERR("PSP2 video could not be initialized.\n"); return (void*)-1; } static bool psp2_frame(void *data, const void *frame, unsigned width, unsigned height, unsigned pitch, const char *msg) { psp2_video_t *psp = (psp2_video_t*)data; update(psp); renderOffscreen(psp); renderMain(psp); cycleDisplayBuffers(psp, FLIP_MODE_VSYNC, width, heigh, pitch); return true; } static void psp2_set_nonblock_state(void *data, bool toggle) { psp2_video_t *psp = (psp2_video_t*)data; psp->vsync = !toggle; } static bool psp2_alive(void *data) { (void)data; return true; } static bool psp2_focus(void *data) { (void)data; return true; } static void psp2_free(void *data) { psp2_video_t *psp = (psp2_video_t*)data; (void)psp; // wait until rendering is done sceGxmFinish(psp->g_context); // destroy graphics data destroyOffscreenBuffer(psp); destroyCubeData(psp); destroyTestTextureData(psp); destroyClearData(psp); // terminate graphics int err = SCE_OK; (void)err; // destroy render target destroyRenderTarget(psp, psp->g_mainRenderTarget); // destroy depth buffer gmmFree(g_mainDepthBufferUid); // wait for display processing to finish before deallocating buffers err = sceGxmDisplayQueueFinish(); assert(err == SCE_OK); // free the display buffers and sync objects for (uint32_t i = 0; i < DISPLAY_BUFFER_COUNT; ++i) { // clear the buffer and deallocate it memset(psp->g_displayBufferData[i], 0, DISPLAY_HEIGHT*DISPLAY_STRIDE_IN_PIXELS*4); gmmFree(psp->g_displayBufferUid[i]); // destroy sync object err = sceGxmSyncObjectDestroy(psp->g_displayBufferSync[i]); assert(err == SCE_OK); } // destroy the shader patcher err = sceGxmShaderPatcherDestroy(psp->g_shaderPatcher); assert(err == SCE_OK); combinedUsseFree(g_patcherCombinedUsseUid); gmmFree(g_patcherBufferUid); // destroy the rendering context err = sceGxmDestroyContext(psp->g_context); assert(err == SCE_OK); fragmentUsseFree(g_fragmentUsseRingBufferUid); gmmFree(g_fragmentRingBufferUid); gmmFree(g_vertexRingBufferUid); gmmFree(g_vdmRingBufferUid); free(g_contextHostMem); // terminate libgxm err = sceGxmTerminate(); assert(err == SCE_OK); sceKernelFreeMemBlock(g_initializeParameterBufferUid); sceKernelFreeMemBlock(g_initializeDriverUid); free(psp->g_initializeHostMem); } #ifdef HAVE_MENU static void psp2_restart(void) {} #endif static void psp2_set_rotation(void *data, unsigned rotation) { psp2_video_t *psp = (psp2_video_t*)data; psp->rotation = rotation; } const video_driver_t video_psp2 = { psp2_init, psp2_frame, psp2_set_nonblock_state, psp2_alive, psp2_focus, NULL, psp2_free, "psp2", #if defined(HAVE_MENU) psp2_restart, #endif psp2_set_rotation, NULL, NULL, #ifdef HAVE_OVERLAY NULL, #endif NULL, };