void FramebufferManagerGLES::MakePixelTexture(const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, float &u1, float &v1) { if (drawPixelsTex_) { render_->DeleteTexture(drawPixelsTex_); } drawPixelsTex_ = render_->CreateTexture(GL_TEXTURE_2D); drawPixelsTexW_ = width; drawPixelsTexH_ = height; drawPixelsTexFormat_ = srcPixelFormat; // TODO: We can just change the texture format and flip some bits around instead of this. // Could share code with the texture cache perhaps. u32 neededSize = width * height * 4; u8 *convBuf = new u8[neededSize]; for (int y = 0; y < height; y++) { const u16_le *src16 = (const u16_le *)srcPixels + srcStride * y; const u32_le *src32 = (const u32_le *)srcPixels + srcStride * y; u32 *dst = (u32 *)convBuf + width * y; switch (srcPixelFormat) { case GE_FORMAT_565: ConvertRGBA565ToRGBA8888((u32 *)dst, src16, width); break; case GE_FORMAT_5551: ConvertRGBA5551ToRGBA8888((u32 *)dst, src16, width); break; case GE_FORMAT_4444: ConvertRGBA4444ToRGBA8888((u32 *)dst, src16, width); break; case GE_FORMAT_8888: memcpy(dst, src32, 4 * width); break; case GE_FORMAT_INVALID: _dbg_assert_msg_(G3D, false, "Invalid pixelFormat passed to DrawPixels()."); break; } } render_->TextureImage(drawPixelsTex_, 0, width, height, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, convBuf, GLRAllocType::NEW, false); render_->FinalizeTexture(drawPixelsTex_, 0, false); // TODO: Return instead? render_->BindTexture(TEX_SLOT_PSP_TEXTURE, drawPixelsTex_); }
// Copies RGBA8 data from RAM to the currently bound render target. void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { float dstwidth = (float)PSP_CoreParameter().pixelWidth; float dstheight = (float)PSP_CoreParameter().pixelHeight; T3DViewport viewport = {0.0f, 0.0f, dstwidth, dstheight, 0.0f, 1.0f}; thin3d->SetViewports(1, &viewport); thin3d->SetBlendState(thin3d->GetBlendStatePreset(BS_OFF)); Thin3DSamplerState *sampler; if (g_Config.iBufFilter == SCALE_NEAREST) { sampler = thin3d->GetSamplerStatePreset(T3DSamplerStatePreset::SAMPS_NEAREST); } else { sampler = thin3d->GetSamplerStatePreset(T3DSamplerStatePreset::SAMPS_LINEAR); } thin3d->SetSamplerStates(0, 1, &sampler); thin3d->SetDepthStencilState(depth); thin3d->SetRenderState(T3DRenderState::CULL_MODE, T3DCullMode::NO_CULL); thin3d->SetScissorEnabled(false); float u0 = 0.0f; float u1; if (displayFramebuf_ == 0) { u8 data[] = {0, 0, 0, 0}; fbTex->SetImageData(0, 0, 0, 1, 1, 1, 0, 4, data); u1 = 1.0f; } else if (displayFormat_ == GE_FORMAT_8888) { u8 *data = Memory::GetPointer(displayFramebuf_); fbTex->SetImageData(0, 0, 0, displayStride_, srcheight, 1, 0, displayStride_ * 4, data); u1 = (float)srcwidth / displayStride_; } else { // TODO: This should probably be converted in a shader instead.. fbTexBuffer.resize(srcwidth * srcheight); FormatBuffer displayBuffer; displayBuffer.data = Memory::GetPointer(displayFramebuf_); for (int y = 0; y < srcheight; ++y) { u32 *buf_line = &fbTexBuffer[y * srcwidth]; const u16 *fb_line = &displayBuffer.as16[y * displayStride_]; switch (displayFormat_) { case GE_FORMAT_565: ConvertRGBA565ToRGBA8888(buf_line, fb_line, srcwidth); break; case GE_FORMAT_5551: ConvertRGBA5551ToRGBA8888(buf_line, fb_line, srcwidth); break; case GE_FORMAT_4444: ConvertRGBA4444ToRGBA8888(buf_line, fb_line, srcwidth); break; default: ERROR_LOG_REPORT(G3D, "Software: Unexpected framebuffer format: %d", displayFormat_); } } fbTex->SetImageData(0, 0, 0, srcwidth, srcheight, 1, 0, srcwidth * 4, (const uint8_t *)&fbTexBuffer[0]); u1 = 1.0f; } fbTex->Finalize(0); float x, y, w, h; CenterDisplayOutputRect(&x, &y, &w, &h, 480.0f, 272.0f, dstwidth, dstheight, ROTATION_LOCKED_HORIZONTAL); if (GetGPUBackend() == GPUBackend::DIRECT3D9) { x += 0.5f; y += 0.5f; } x /= 0.5f * dstwidth; y /= 0.5f * dstheight; w /= 0.5f * dstwidth; h /= 0.5f * dstheight; float x2 = x + w; float y2 = y + h; x -= 1.0f; y -= 1.0f; x2 -= 1.0f; y2 -= 1.0f; struct Vertex { float x, y, z; float u, v; uint32_t rgba; }; float v0 = 1.0f; float v1 = 0.0f; if (GetGPUBackend() == GPUBackend::VULKAN) { std::swap(v0, v1); } const Vertex verts[4] = { {x, y, 0, u0, v0, 0xFFFFFFFF}, // TL {x, y2, 0, u0, v1, 0xFFFFFFFF}, // BL {x2, y2, 0, u1, v1, 0xFFFFFFFF}, // BR {x2, y, 0, u1, v0, 0xFFFFFFFF}, // TR }; vdata->SetData((const uint8_t *)verts, sizeof(verts)); int indexes[] = {0, 1, 2, 0, 2, 3}; idata->SetData((const uint8_t *)indexes, sizeof(indexes)); thin3d->SetTexture(0, fbTex); Thin3DShaderSet *texColor = thin3d->GetShaderSetPreset(SS_TEXTURE_COLOR_2D); static const float identity4x4[16] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; texColor->SetMatrix4x4("WorldViewProj", identity4x4); thin3d->DrawIndexed(T3DPrimitive::PRIM_TRIANGLES, texColor, vformat, vdata, idata, 6, 0); }
void TextureReplacer::NotifyTextureDecoded(const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int w, int h) { _assert_msg_(G3D, enabled_, "Replacement not enabled"); if (!g_Config.bSaveNewTextures) { // Ignore. return; } if (replacedInfo.addr > 0x05000000 && replacedInfo.addr < 0x08800000) { // Don't save the PPGe texture. return; } if (replacedInfo.isVideo && !allowVideo_) { return; } std::string hashfile = LookupHashFile(replacedInfo.cachekey, replacedInfo.hash, level); const std::string filename = basePath_ + hashfile; const std::string saveFilename = basePath_ + NEW_TEXTURE_DIR + hashfile; // If it's empty, it's an ignored hash, we intentionally don't save. if (hashfile.empty() || File::Exists(filename)) { // If it exists, must've been decoded and saved as a new texture already. return; } ReplacementCacheKey replacementKey(replacedInfo.cachekey, replacedInfo.hash); auto it = savedCache_.find(replacementKey); if (it != savedCache_.end() && File::Exists(saveFilename)) { // We've already saved this texture. Let's only save if it's bigger (e.g. scaled now.) if (it->second.w >= w && it->second.h >= h) { return; } } #ifdef _WIN32 size_t slash = hashfile.find_last_of("/\\"); #else size_t slash = hashfile.find_last_of("/"); #endif if (slash != hashfile.npos) { // Create any directory structure as needed. const std::string saveDirectory = basePath_ + NEW_TEXTURE_DIR + hashfile.substr(0, slash); if (!File::Exists(saveDirectory)) { File::CreateFullPath(saveDirectory); } } // Only save the hashed portion of the PNG. int lookupW = w / replacedInfo.scaleFactor; int lookupH = h / replacedInfo.scaleFactor; if (LookupHashRange(replacedInfo.addr, lookupW, lookupH)) { w = lookupW * replacedInfo.scaleFactor; h = lookupH * replacedInfo.scaleFactor; } #ifdef USING_QT_UI ERROR_LOG(G3D, "Replacement texture saving not implemented for Qt"); #else if (replacedInfo.fmt != ReplacedTextureFormat::F_8888) { saveBuf.resize((pitch * h) / sizeof(u16)); switch (replacedInfo.fmt) { case ReplacedTextureFormat::F_5650: ConvertRGBA565ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); break; case ReplacedTextureFormat::F_5551: ConvertRGBA5551ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); break; case ReplacedTextureFormat::F_4444: ConvertRGBA4444ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); break; case ReplacedTextureFormat::F_0565_ABGR: ConvertABGR565ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); break; case ReplacedTextureFormat::F_1555_ABGR: ConvertABGR1555ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); break; case ReplacedTextureFormat::F_4444_ABGR: ConvertABGR4444ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16)); break; case ReplacedTextureFormat::F_8888_BGRA: ConvertBGRA8888ToRGBA8888(saveBuf.data(), (const u32 *)data, (pitch * h) / sizeof(u32)); break; case ReplacedTextureFormat::F_8888: // Impossible. Just so we can get warnings on other missed formats. break; } data = saveBuf.data(); if (replacedInfo.fmt != ReplacedTextureFormat::F_8888_BGRA) { // We doubled our pitch. pitch *= 2; } } png_image png; memset(&png, 0, sizeof(png)); png.version = PNG_IMAGE_VERSION; png.format = PNG_FORMAT_RGBA; png.width = w; png.height = h; bool success = WriteTextureToPNG(&png, saveFilename, 0, data, pitch, nullptr); png_image_free(&png); if (png.warning_or_error >= 2) { ERROR_LOG(COMMON, "Saving screenshot to PNG produced errors."); } else if (success) { NOTICE_LOG(G3D, "Saving texture for replacement: %08x / %dx%d", replacedInfo.hash, w, h); } #endif // Remember that we've saved this for next time. ReplacedTextureLevel saved; saved.fmt = ReplacedTextureFormat::F_8888; saved.file = filename; saved.w = w; saved.h = h; savedCache_[replacementKey] = saved; }