/* The texture cache works for the renderer: it reads the registers the * frame is rendered with, not the ones the emulation has moved on to. */ #define PVR_REGS_FOR_RENDERER #include "TexCache.h" #include "lockfree.h" #include "CustomTexture.h" #ifdef HAVE_TEXUPSCALE #include "deps/xbrz/xbrz.h" #endif #include "hw/pvr/pvr_mem.h" #include "hw/mem/_vmem.h" #include "hw/mem/vmem32.h" #include "hw/sh4/modules/mmu.h" #include // for a hash state that can be a local: ComputeHash() allocates nothing #define XXH_STATIC_LINKING_ONLY #include #if defined(HAVE_TEXUPSCALE) && !defined(TARGET_NO_OPENMP) #include #endif u8* vq_codebook; u32 palette_index; bool KillTex=false; u32 palette16_ram[1024]; u32 palette32_ram[1024]; u32 pal_hash_256[4]; u32 pal_hash_16[64]; bool palette_updated; // Rough approximation of LoD bias from D adjust param, only used to increase LoD const std::array D_Adjust_LoD_Bias = { 0.f, -4.f, -2.f, -1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f }; static void rend_text_invl(vram_block* bl); u32 detwiddle[2][11][1024]; //input : address in the yyyyyxxxxx format //output : address in the xyxyxyxy format //U : x resolution , V : y resolution //twiddle works on 64b words static u32 twiddle_slow(u32 x,u32 y,u32 x_sz,u32 y_sz) { u32 rv=0;//low 2 bits are directly passed -> needs some misc stuff to work.However //Pvr internally maps the 64b banks "as if" they were twiddled :p u32 sh=0; x_sz>>=1; y_sz>>=1; while(x_sz!=0 || y_sz!=0) { if (y_sz) { u32 temp=y&1; rv|=temp<>=1; y>>=1; sh++; } if (x_sz) { u32 temp=x&1; rv|=temp<>=1; x>>=1; sh++; } } return rv; } static void BuildTwiddleTables() { for (u32 s = 0; s < 11; s++) { u32 x_sz = 1024; u32 y_sz = 1 << s; for (u32 i = 0; i < x_sz; i++) { detwiddle[0][s][i] = twiddle_slow(i, 0, x_sz, y_sz); detwiddle[1][s][i] = twiddle_slow(0, i, y_sz, x_sz); } } } static OnLoad btt(&BuildTwiddleTables); /* Converts the palette for the renderer when it is not the one converted * last. Called by the renderer for each frame, with the registers that * frame is drawn with. * * This used to be driven by a flag the register writes set, and was run * by the emulation thread when the game started a render. With Threaded * Rendering the frame before could still be drawing then, reading the very * tables this rewrites. Now the tables belong to the thread that draws, and * whether they are stale is seen from the palette itself: 4 KB to compare. */ void palette_update() { static u32 converted[1024]; static u32 converted_ctrl = 0xFFFFFFFF; const u32 ctrl = PAL_RAM_CTRL & 3; if (ctrl == converted_ctrl && !memcmp(converted, PALETTE_RAM, sizeof(converted))) return; converted_ctrl = ctrl; memcpy(converted, PALETTE_RAM, sizeof(converted)); palette_updated = true; switch (ctrl) { case 0: for (int i=0;i<1024;i++) { palette16_ram[i] = ARGB1555(PALETTE_RAM[i]); palette32_ram[i] = ARGB1555_32(PALETTE_RAM[i]); } break; case 1: for (int i=0;i<1024;i++) { palette16_ram[i] = ARGB565(PALETTE_RAM[i]); palette32_ram[i] = ARGB565_32(PALETTE_RAM[i]); } break; case 2: for (int i=0;i<1024;i++) { palette16_ram[i] = ARGB4444(PALETTE_RAM[i]); palette32_ram[i] = ARGB4444_32(PALETTE_RAM[i]); } break; case 3: for (int i=0;i<1024;i++) { palette16_ram[i] = ARGB8888(PALETTE_RAM[i]); palette32_ram[i] = ARGB8888_32(PALETTE_RAM[i]); } break; } for (int i = 0; i < 64; i++) pal_hash_16[i] = XXH32(&PALETTE_RAM[i << 4], 16 * 4, 7); for (int i = 0; i < 4; i++) pal_hash_256[i] = XXH32(&PALETTE_RAM[i << 8], 256 * 4, 7); } static std::vector VramLocks[VRAM_SIZE_MAX / PAGE_SIZE]; VArray2 vram; // vram 32-64b //List functions // void vramlock_list_remove(vram_block* block) { u32 base = block->start / PAGE_SIZE; u32 end = block->end / PAGE_SIZE; for (u32 i = base; i <= end; i++) { std::vector& list = VramLocks[i]; for (auto& lock : list) { if (lock == block) lock = nullptr; } } } void vramlock_list_add(vram_block* block) { u32 base = block->start / PAGE_SIZE; u32 end = block->end / PAGE_SIZE; for (u32 i = base; i <= end; i++) { std::vector& list = VramLocks[i]; // If the list is empty then we need to protect vram, otherwise it's already been done if (list.empty() || std::all_of(list.begin(), list.end(), [](vram_block *block) { return block == nullptr; })) _vmem_protect_vram(i * PAGE_SIZE, PAGE_SIZE); auto it = std::find(list.begin(), list.end(), nullptr); if (it != list.end()) *it = block; else list.push_back(block); } } /* Writes to protected video memory, by page. * * The lock lists above belong to the render thread; nothing else reads or * changes them. The emulation thread finds out that a protected page was * written when the write faults, possibly inside a signal handler, and all * it does there is open the page up again and mark it here. The render * thread collects the marks before it looks at any texture and drops every * lock on those pages, which is what marks their textures as changed. * * The emulation thread unprotects first and marks second. A page it has * opened up is therefore always marked afterwards, so the render thread * can go on treating "a page with locks on it is protected" as true: when * it is not, a mark is on its way that takes those locks off again. */ static cMarkSet vram_writes; static void libCore_vramlock_Unlock_block_wb(vram_block* block); /* Render thread: apply the writes the emulation thread has marked. */ static void vramlock_collect() { vram_writes.Collect([](unsigned page) { std::vector& list = VramLocks[page]; for (auto& lock : list) { if (lock != nullptr) { rend_text_invl(lock); if (lock != nullptr) { ERROR_LOG(PVR, "Error : pvr is supposed to remove lock"); die("Invalid state"); } } } list.clear(); }); } void libCore_vramlock_Lock(u32 start_offset64, u32 end_offset64, BaseTextureCacheData *texture) { if (end_offset64>(VRAM_SIZE-1)) { WARN_LOG(PVR, "vramlock_Lock_64: end_offset64>(VRAM_SIZE-1) \n Tried to lock area out of vram , possibly bug on the pvr plugin"); end_offset64=(VRAM_SIZE-1); } if (start_offset64>end_offset64) { WARN_LOG(PVR, "vramlock_Lock_64: start_offset64>end_offset64 \n Tried to lock negative block , possibly bug on the pvr plugin"); start_offset64=0; } /* Writes that came before this lock are not its business. */ vramlock_collect(); if (texture->lock_block == nullptr) { vram_block* block=(vram_block* )malloc(sizeof(vram_block)); block->end=end_offset64; block->start=start_offset64; block->len=end_offset64-start_offset64+1; block->userdata = texture; block->type=64; // This also protects vram if needed vramlock_list_add(block); texture->lock_block = block; } } /* A write to video memory hit a protected page. Called on the emulation * thread from the fault handler, so it stays within what a signal handler * may do: system calls and atomics, no locks and no allocation. The render * thread calls it too, before it writes to video memory itself. */ bool VramLockedWriteOffset(size_t offset) { if (offset >= VRAM_SIZE) return false; _vmem_unprotect_vram((u32)(offset & ~PAGE_MASK), PAGE_SIZE); vram_writes.Mark((unsigned)(offset / PAGE_SIZE)); return true; } bool VramLockedWrite(u8* address) { u32 offset = _vmem_get_vram_offset(address); if (offset == (u32)-1) return false; return VramLockedWriteOffset(offset); } //unlocks mem //also frees the handle static void libCore_vramlock_Unlock_block_wb(vram_block* block) { vramlock_list_remove(block); free(block); } #ifdef HAVE_TEXUPSCALE #ifndef TARGET_NO_OPENMP static inline int getThreadCount() { int tcount = omp_get_num_procs() - 1; if (tcount < 1) tcount = 1; return std::min(tcount, 3); } template void parallelize(Func func, int start, int end) { int tcount = getThreadCount(); #pragma omp parallel num_threads(tcount) { int num_threads = omp_get_num_threads(); int thread = omp_get_thread_num(); int chunk = (end - start) / num_threads; func(start + chunk * thread, num_threads == thread + 1 ? end : (start + chunk * (thread + 1))); } } #endif static struct xbrz::ScalerCfg xbrz_cfg; void UpscalexBRZ(int factor, u32* source, u32* dest, int width, int height, bool has_alpha) { #ifndef TARGET_NO_OPENMP parallelize([=](int start, int end) { xbrz::scale(factor, source, dest, width, height, has_alpha ? xbrz::ColorFormat::ARGB : xbrz::ColorFormat::RGB, xbrz_cfg, start, end); }, 0, height); #else xbrz::scale(factor, source, dest, width, height, has_alpha ? xbrz::ColorFormat::ARGB : xbrz::ColorFormat::RGB, xbrz_cfg); #endif } #endif struct PvrTexInfo { const char* name; int bpp; //4/8 for pal. 16 for yuv, rgb, argb TextureType type; // Conversion to 16 bpp TexConvFP *PL; TexConvFP *TW; TexConvFP *VQ; // Conversion to 32 bpp TexConvFP32 *PL32; TexConvFP32 *TW32; TexConvFP32 *VQ32; // Conversion to 8 bpp (palette) TexConvFP8 *TW8; // Planar and compressed, to 32 bpp TexConvFP32 *PLVQ32; }; static const PvrTexInfo format[8] = { // name bpp Final format Planar Twiddled VQ Planar(32b) Twiddled(32b) VQ (32b) Palette (8b) {"1555", 16, TextureType::_5551, tex1555_PL, tex1555_TW, tex1555_VQ, tex1555_PL32, tex1555_TW32, tex1555_VQ32, nullptr, tex1555_PLVQ32 }, //1555 {"565", 16, TextureType::_565, tex565_PL, tex565_TW, tex565_VQ, tex565_PL32, tex565_TW32, tex565_VQ32, nullptr, tex565_PLVQ32 }, //565 {"4444", 16, TextureType::_4444, tex4444_PL, tex4444_TW, tex4444_VQ, tex4444_PL32, tex4444_TW32, tex4444_VQ32, nullptr, tex4444_PLVQ32 }, //4444 {"yuv", 16, TextureType::_8888, nullptr, nullptr, nullptr, texYUV422_PL, texYUV422_TW, texYUV422_VQ, nullptr, texYUV422_PLVQ }, //yuv {"bumpmap", 16, TextureType::_4444, texBMP_PL, texBMP_TW, texBMP_VQ, tex4444_PL32, tex4444_TW32, tex4444_VQ32, nullptr, tex4444_PLVQ32 }, //bump map {"pal4", 4, TextureType::_5551, nullptr, texPAL4_TW, texPAL4_VQ, nullptr, texPAL4_TW32, texPAL4_VQ32, texPAL4PT_TW }, //pal4 {"pal8", 8, TextureType::_5551, nullptr, texPAL8_TW, texPAL8_VQ, nullptr, texPAL8_TW32, texPAL8_VQ32, texPAL8PT_TW }, //pal8 {"ns/1555", 0}, // Not supported (1555) }; static const u32 VQMipPoint[11] = { 0x00000,//1 0x00001,//2 0x00002,//4 0x00006,//8 0x00016,//16 0x00056,//32 0x00156,//64 0x00556,//128 0x01556,//256 0x05556,//512 0x15556//1024 }; static const u32 OtherMipPoint[11] = { 0x00003,//1 0x00004,//2 0x00008,//4 0x00018,//8 0x00058,//16 0x00158,//32 0x00558,//64 0x01558,//128 0x05558,//256 0x15558,//512 0x55558//1024 }; static const TextureType PAL_TYPE[4] = { TextureType::_5551, TextureType::_565, TextureType::_4444, TextureType::_8888 }; void BaseTextureCacheData::PrintTextureName() { char str[512]; sprintf(str, "Texture: %s", GetPixelFormatName()); if (tcw.VQ_Comp) strcat(str, " VQ"); else if (tcw.ScanOrder == 0) strcat(str, " TW"); else if (tcw.StrideSel) strcat(str, " Stride"); if (tcw.ScanOrder == 0 && tcw.MipMapped) strcat(str, " MM"); if (tsp.FilterMode != 0) strcat(str, " Bilinear"); sprintf(str + strlen(str), " %dx%d @ 0x%X", 8 << tsp.TexU, 8 << tsp.TexV, tcw.TexAddr << 3); std::string id = GetId(); sprintf(str + strlen(str), " id=%s", id.c_str()); DEBUG_LOG(RENDERER, "%s", str); } /* Hash of the vram content backing this texture, over the same range the * write-protection lock would cover, clamped to VRAM like the lock is. Used to * detect changes for frequently-updated textures that run unprotected. */ u32 BaseTextureCacheData::ComputeVramHash() { u32 start = sa_tex & VRAM_MASK; u32 end = sa + size; if (end > VRAM_SIZE) end = VRAM_SIZE; if (end <= start) return 0; return XXH32(&vram[start], end - start, 7); } //true if : dirty or paletted texture and hashes don't match bool BaseTextureCacheData::NeedsUpdate() { /* Take in what the emulation thread has written since the last look. */ if (vram_writes.Pending()) vramlock_collect(); bool rc = dirty != 0; if (tex_type != TextureType::_8) { if (tcw.PixelFmt == PixelPal4 && palette_hash != pal_hash_16[tcw.PalSelect]) rc = true; else if (tcw.PixelFmt == PixelPal8 && palette_hash != pal_hash_256[tcw.PalSelect >> 4]) rc = true; } /* Frequently-updated texture running unprotected (see Update): compare the * content hash of the vram range instead of relying on a write fault. */ if (!rc && lock_block == nullptr && Updates >= FREQUENT_UPDATE_COUNT && ComputeVramHash() != vram_hash) rc = true; return rc; } bool BaseTextureCacheData::Delete() { if (retro_atomic_load_acquire_int(&custom_load_in_progress) > 0) return false; if (lock_block) libCore_vramlock_Unlock_block_wb(lock_block); lock_block = nullptr; free(custom_image_data); return true; } void BaseTextureCacheData::Create() { //Reset state info .. Updates = 0; dirty = FrameCount; lock_block = nullptr; custom_image_data = nullptr; retro_atomic_store_release_int(&custom_load_in_progress, 0); retro_atomic_int_init(&custom_request_hash, 0); retro_atomic_int_init(&custom_request_old_hash, 0); retro_atomic_int_init(&custom_request_old_vqhash, 0); //decode info from tsp/tcw into the texture struct tex = &format[tcw.PixelFmt == PixelReserved ? Pixel1555 : tcw.PixelFmt]; //texture format table entry sa_tex = (tcw.TexAddr << 3) & VRAM_MASK; //texture start address sa = sa_tex; //data texture start address (modified for MIPs, as needed) w = 8 << tsp.TexU; //tex width h = 8 << tsp.TexV; //tex height //PAL texture if (tex->bpp == 4) palette_index = tcw.PalSelect << 4; else if (tex->bpp == 8) palette_index = (tcw.PalSelect >> 4) << 8; texconv8 = nullptr; /* Compressed and mipmapped, a texture is twiddled whatever this bit * says: Star Wars Demolition sets it, and its textures came out * corrupted (upstream: or took the emulator down). Not for a paletted * texture, where the bit is part of the palette selection. */ if (tcw.VQ_Comp == 1 && tcw.MipMapped == 1 && !IsPaletted()) tcw.ScanOrder = 0; if (tcw.ScanOrder && (tex->PL || tex->PL32)) { //Texture is stored 'planar' in memory, no deswizzle is needed if (tcw.MipMapped != 0) { WARN_LOG(RENDERER, "Warning: planar texture with mipmaps (invalid)"); tcw.MipMapped = 0; } //Planar textures support stride selection, mostly used for non power of 2 textures (videos) int stride = w; if (tcw.StrideSel) { stride = (TEXT_CONTROL & 31) * 32; // a stride of nothing is no stride: the texture's own width if (stride == 0) stride = w; } if (tcw.VQ_Comp != 0 && tex->PLVQ32 != nullptr) { /* Compressed as well. This was taken for a mistake and the * compression ignored; it is a texture like any other (the * Genesis emulator in the Teenage Mutant Ninja Turtles * collection draws its screen with one). */ vq_codebook = sa; texconv = nullptr; texconv32 = tex->PLVQ32; size = 256 * 4 * 2 + stride * h / 4; } else { tcw.VQ_Comp = 0; //Call the format specific conversion code texconv = tex->PL; texconv32 = tex->PL32; //calculate the size, in bytes, for the locking size = stride * h * tex->bpp / 8; } } else { /* These two bits mean nothing for a twiddled texture and are taken * out of the way - unless the texture is paletted. There they are * the top two bits of the palette selection, which says which part * of the palette the texture uses. With them cleared, NeedsUpdate() * looked for changes in the wrong part: a texture using the upper * half of the palette was not decoded again when its colours * changed, and was when some other texture's did. */ if (!IsPaletted()) { tcw.ScanOrder = 0; tcw.StrideSel = 0; } // Quake 3 Arena uses one if (tcw.MipMapped) // Mipmapped texture must be square and TexV is ignored h = w; if (tcw.VQ_Comp) { verify(tex->VQ != NULL || tex->VQ32 != NULL); vq_codebook = sa; if (tcw.MipMapped) sa += VQMipPoint[tsp.TexU + 3]; texconv = tex->VQ; texconv32 = tex->VQ32; /* From sa: the codebook the converters skip, and a byte for * every four pixels. This was half the pixels' share and no * codebook, so a write to the rest of the texture was not * watched for, and the texture stayed as it was. */ size = 256 * 4 * 2 + w * h / 4; } else { verify(tex->TW != NULL || tex->TW32 != NULL); if (tcw.MipMapped) sa += OtherMipPoint[tsp.TexU + 3] * tex->bpp / 8; texconv = tex->TW; texconv32 = tex->TW32; size = w * h * tex->bpp / 8; texconv8 = tex->TW8; } } } /* Where a texture is converted before it is handed to the GPU. One piece of * memory, kept from one texture to the next and only ever grown: every * update used to allocate a buffer the size of its texture and free it * again a moment later, sixty megabytes a second of that for a video * playing as a 512x512 texture. Whatever is in it is the last texture's and * of no use, so growing it copies nothing. Updates happen on the one thread * that renders. At most a 1024x1024 texture with its mipmaps, 5.6 MB. */ static void *tex_scratch; static size_t tex_scratch_size; static void *texcache_scratch(size_t size) { if (size > tex_scratch_size) { free(tex_scratch); tex_scratch = malloc(size); tex_scratch_size = tex_scratch != NULL ? size : 0; } return tex_scratch; } void texcache_scratch_free() { free(tex_scratch); tex_scratch = NULL; tex_scratch_size = 0; } void BaseTextureCacheData::ComputeHash() { // twiddled and compressed; for a paletted texture the scan order bit is part of the palette selection if (tcw.VQ_Comp && !(tcw.ScanOrder && (tex->PL || tex->PL32))) { /* A compressed texture's name used to come from the first w*h/8 * bytes at sa - part of the codebook and none or some of the * texture, so that two textures could share one. It is now the * hash of the codebook and then of the whole texture, as upstream * names them; the old name is kept, and custom textures saved * under it are still found. */ XXH32_state_t state; old_vqtexture_hash = XXH32(&vram[sa], w * h / 8, 7); if (IsPaletted()) old_vqtexture_hash ^= palette_hash; old_texture_hash = old_vqtexture_hash; old_vqtexture_hash ^= tcw.full & 0xFC000000; XXH32_reset(&state, 7); XXH32_update(&state, &vram[sa_tex], 256 * 4 * 2); XXH32_update(&state, &vram[sa + 256 * 4 * 2], w * h / 4); texture_hash = XXH32_digest(&state); if (IsPaletted()) texture_hash ^= palette_hash; texture_hash ^= tcw.full & 0xFC000000; return; } old_vqtexture_hash = 0; texture_hash = XXH32(&vram[sa], size, 7); if (IsPaletted()) texture_hash ^= palette_hash; old_texture_hash = texture_hash; texture_hash ^= tcw.full & 0xFC000000; // everything but texaddr, reserved and stride } void BaseTextureCacheData::Update() { //texture state tracking stuff Updates++; dirty=0; tex_type = tex->type; bool has_alpha = false; if (IsPaletted()) { if (IsGpuHandledPaletted(tsp, tcw, area)) tex_type = TextureType::_8; else { tex_type = PAL_TYPE[PAL_RAM_CTRL&3]; if (tex_type != TextureType::_565) has_alpha = true; } // Get the palette hash to check for future updates if (tcw.PixelFmt == PixelPal4) palette_hash = pal_hash_16[tcw.PalSelect]; else palette_hash = pal_hash_256[tcw.PalSelect >> 4]; } ::palette_index = this->palette_index; // might be used if pal. tex ::vq_codebook = &vram[vq_codebook]; // might be used if VQ tex //texture conversion work u32 stride = w; if (tcw.StrideSel && tcw.ScanOrder && (tex->PL || tex->PL32)) { stride = (TEXT_CONTROL & 31) * 32; if (stride == 0) stride = w; } /* How many of the texture's lines there are to read. All of them, but * for a texture that runs past the end of video memory: Shenmue's Space * Harrier mini-arcade loads one, and only uses the top of it. * * That was done by cutting the texture's height and size down for the * update and putting the height back after. The size stayed cut - it is * what is locked and hashed, and has to end inside video memory - so the * second update found nothing running past the end, cut nothing, and * read all the lines, the ones outside video memory included. And the * first update converted into a buffer made for the cut height, then * uploaded the whole. (From upstream, done here by what the texture * would take whole rather than by the size as it stands.) */ u32 rows = h; const bool cut_short = tcw.ScanOrder && stride > 0 && !tcw.VQ_Comp && tex->bpp != 0; const u32 whole = cut_short ? stride * h * tex->bpp / 8 : size; if (sa_tex > VRAM_SIZE || size == 0 || sa + (whole > size ? whole : size) > VRAM_SIZE) { if (sa < VRAM_SIZE && cut_short) { rows = (VRAM_SIZE - sa) * 8 / stride / tex->bpp; size = stride * rows * tex->bpp/8; } else { WARN_LOG(RENDERER, "Warning: invalid texture. Address %08X %08X size %d", sa_tex, sa, size); return; } } if (settings.rend.CustomTextures) custom_texture.LoadCustomTextureAsync(this); void *temp_tex_buffer = NULL; u32 upscaled_w = w; u32 upscaled_h = h; PixelBuffer pb16; PixelBuffer pb32; PixelBuffer pb8; /* A planar texture is converted a row of 'stride' pixels at a time into * rows of the texture's width. With a stride wider than the texture, * every row runs on into the next, which is then written over it - and * the last runs past the end of the buffer by the difference: room is * left for that. With a narrower one the rest of each row was whatever * the memory held, and went to the GPU so; it is cleared. */ const u32 stride_over = stride > w ? stride - w : 0; // Figure out if we really need to use a 32-bit pixel buffer bool textureUpscaling = settings.rend.TextureUpscale > 1 // Don't process textures that are too big && (int)(w * h) <= settings.rend.MaxFilteredTextureSize * settings.rend.MaxFilteredTextureSize // Don't process YUV textures && tcw.PixelFmt != PixelYUV; bool need_32bit_buffer = true; if (!textureUpscaling && (!IsPaletted() || tex_type != TextureType::_8888) && texconv != NULL && !Force32BitTexture(tex_type)) need_32bit_buffer = false; // TODO avoid upscaling/depost. textures that change too often bool mipmapped = IsMipmapped() && !settings.rend.DumpTextures; if (texconv32 != NULL && need_32bit_buffer) { if (textureUpscaling) // don't use mipmaps if upscaling mipmapped = false; // Force the texture type since that's the only 32-bit one we know tex_type = TextureType::_8888; if (mipmapped) { pb32.init(w, h, true, texcache_scratch(PixelBuffer::bytes(w, h, true))); for (u32 i = 0; i <= tsp.TexU + 3u; i++) { pb32.set_mipmap(i); u32 vram_addr; if (tcw.VQ_Comp) { vram_addr = sa_tex + VQMipPoint[i]; if (i == 0) { u32 corner[4]; PixelBuffer pb0; pb0.init(2, 2, false, corner); /* The 1x1 level is the corner of a 2x2 block. For a * YUV texture that block is not a colour - it came * out magenta, behind the pitch in World Series * Baseball 2K2 - and the 2x2 level's is used. */ if (tcw.PixelFmt == PixelYUV) vram_addr = sa_tex + VQMipPoint[1]; texconv32(&pb0, (u8*)&vram[vram_addr], 2, 2); *pb32.data() = *pb0.data(1, 1); continue; } } else vram_addr = sa_tex + OtherMipPoint[i] * tex->bpp / 8; if (tcw.PixelFmt == PixelYUV && i == 0) // Special case for YUV at 1x1 LoD format[Pixel565].TW32(&pb32, &vram[vram_addr], 1, 1); else texconv32(&pb32, &vram[vram_addr], 1 << i, 1 << i); } pb32.set_mipmap(0); } else { pb32.init(w, h, false, texcache_scratch(PixelBuffer::bytes(w, h, false) + stride_over * sizeof(u32))); texconv32(&pb32, (u8*)&vram[sa], stride, rows); if (rows < h) memset((u32 *)pb32.data() + rows * w, 0, (size_t)(h - rows) * w * sizeof(u32)); if (stride < w) for (u32 y = 0; y < h; y++) memset(pb32.data(stride, y), 0, (w - stride) * sizeof(u32)); #ifdef HAVE_TEXUPSCALE // xBRZ scaling if (textureUpscaling) { PixelBuffer tmp_buf; tmp_buf.init(w * settings.rend.TextureUpscale, h * settings.rend.TextureUpscale); if (tcw.PixelFmt == Pixel1555 || tcw.PixelFmt == Pixel4444) // Alpha channel formats. Palettes with alpha are already handled has_alpha = true; UpscalexBRZ(settings.rend.TextureUpscale, pb32.data(), tmp_buf.data(), w, h, has_alpha); pb32.steal_data(tmp_buf); upscaled_w *= settings.rend.TextureUpscale; upscaled_h *= settings.rend.TextureUpscale; } #endif } temp_tex_buffer = pb32.data(); } else if (texconv8 != NULL && tex_type == TextureType::_8) { if (mipmapped) { pb8.init(w, h, true, texcache_scratch(PixelBuffer::bytes(w, h, true))); for (u32 i = 0; i <= tsp.TexU + 3u; i++) { pb8.set_mipmap(i); u32 vram_addr = sa_tex + OtherMipPoint[i] * tex->bpp / 8; texconv8(&pb8, &vram[vram_addr], 1 << i, 1 << i); } pb8.set_mipmap(0); } else { pb8.init(w, h, false, texcache_scratch(PixelBuffer::bytes(w, h, false))); texconv8(&pb8, &vram[sa], stride, rows); if (rows < h) memset((u8 *)pb8.data() + rows * w, 0, (size_t)(h - rows) * w); } temp_tex_buffer = pb8.data(); } else if (texconv != NULL) { if (mipmapped) { pb16.init(w, h, true, texcache_scratch(PixelBuffer::bytes(w, h, true))); for (u32 i = 0; i <= tsp.TexU + 3u; i++) { pb16.set_mipmap(i); u32 vram_addr; if (tcw.VQ_Comp) { vram_addr = sa_tex + VQMipPoint[i]; if (i == 0) { u16 corner[4]; PixelBuffer pb0; pb0.init(2, 2, false, corner); texconv(&pb0, (u8*)&vram[vram_addr], 2, 2); *pb16.data() = *pb0.data(1, 1); continue; } } else vram_addr = sa_tex + OtherMipPoint[i] * tex->bpp / 8; texconv(&pb16, (u8*)&vram[vram_addr], 1 << i, 1 << i); } pb16.set_mipmap(0); } else { pb16.init(w, h, false, texcache_scratch(PixelBuffer::bytes(w, h, false) + stride_over * sizeof(u16))); texconv(&pb16,(u8*)&vram[sa],stride,rows); if (rows < h) memset((u16 *)pb16.data() + rows * w, 0, (size_t)(h - rows) * w * sizeof(u16)); if (stride < w) for (u32 y = 0; y < h; y++) memset(pb16.data(stride, y), 0, (w - stride) * sizeof(u16)); } temp_tex_buffer = pb16.data(); } else { //fill it in with a temp color WARN_LOG(RENDERER, "UNHANDLED TEXTURE"); pb16.init(w, h, false, texcache_scratch(PixelBuffer::bytes(w, h, false))); memset(pb16.data(), 0x80, w * h * 2); temp_tex_buffer = pb16.data(); mipmapped = false; } //lock the texture to detect changes in it, unless it is frequently //updated: each update of a protected texture costs a SIGSEGV (the write //fault), the invalidation, a full re-upload, and a re-protect -- measured //as 1-2 faults per frame sustained, with matching frame-time jitter, on //titles that rewrite textures every frame (e.g. VF3tb). Past the update //threshold, leave the pages writable and detect changes with a content //hash at lookup time instead (see NeedsUpdate): a hash over the same range //is cheaper than the fault path, and when the game rewrites identical //content it avoids the re-upload entirely. if (Updates < FREQUENT_UPDATE_COUNT) libCore_vramlock_Lock(sa_tex, sa + size - 1, this); else vram_hash = ComputeVramHash(); UploadToGPU(upscaled_w, upscaled_h, (u8*)temp_tex_buffer, IsMipmapped(), mipmapped); if (settings.rend.DumpTextures) { ComputeHash(); custom_texture.DumpTexture(texture_hash, upscaled_w, upscaled_h, tex_type, temp_tex_buffer); NOTICE_LOG(RENDERER, "Dumped texture %x.png. Old hash %x", texture_hash, old_texture_hash); } PrintTextureName(); } void BaseTextureCacheData::CheckCustomTexture() { if (IsCustomTextureAvailable()) { tex_type = TextureType::_8888; UploadToGPU(custom_width, custom_height, custom_image_data, IsMipmapped(), false); free(custom_image_data); custom_image_data = NULL; } } void ReadFramebuffer(PixelBuffer& pb, int& width, int& height) { width = (FB_R_SIZE.fb_x_size + 1) << 1; // in 16-bit words height = FB_R_SIZE.fb_y_size + 1; int modulus = (FB_R_SIZE.fb_modulus - 1) << 1; int bpp; switch (FB_R_CTRL.fb_depth) { case FBDE_0555: case FBDE_565: bpp = 2; break; case FBDE_888: bpp = 3; width = (width * 2) / 3; // in pixels modulus = (modulus * 2) / 3; // in pixels break; case FBDE_C888: bpp = 4; width /= 2; // in pixels modulus /= 2; // in pixels break; default: die("Invalid framebuffer format\n"); bpp = 4; break; } u32 addr = FB_R_SOF1; if (SPG_CONTROL.interlace) { if (width == modulus && FB_R_SOF2 == FB_R_SOF1 + width * bpp) { // Typical case alternating even and odd lines -> take the whole buffer at once modulus = 0; height *= 2; } else { addr = SPG_STATUS.fieldnum ? FB_R_SOF2 : FB_R_SOF1; } } else if (FB_R_CTRL.vclk_div == 0) { /* Not interlaced, on a television: 240 lines are all there are, * whatever the size register says. Upstream: the Echelon intro of * Confidential Mission (PAL). */ height = std::min(height, 240); } // handed to the GPU at once by whoever asked: converted where textures are, nothing allocated pb.init(width, height, false, texcache_scratch(PixelBuffer::bytes(width, height, false))); u8 *dst = (u8*)pb.data(); switch (FB_R_CTRL.fb_depth) { case FBDE_0555: // 555 RGB for (int y = 0; y < height; y++) { for (int i = 0; i < width; i++) { u16 src = pvr_read_area1(addr); *dst++ = (((src >> 10) & 0x1F) << 3) + FB_R_CTRL.fb_concat; *dst++ = (((src >> 5) & 0x1F) << 3) + FB_R_CTRL.fb_concat; *dst++ = (((src >> 0) & 0x1F) << 3) + FB_R_CTRL.fb_concat; *dst++ = 0xFF; addr += bpp; } addr += modulus * bpp; } break; case FBDE_565: // 565 RGB for (int y = 0; y < height; y++) { for (int i = 0; i < width; i++) { u16 src = pvr_read_area1(addr); *dst++ = (((src >> 11) & 0x1F) << 3) + FB_R_CTRL.fb_concat; *dst++ = (((src >> 5) & 0x3F) << 2) + (FB_R_CTRL.fb_concat & 3); *dst++ = (((src >> 0) & 0x1F) << 3) + FB_R_CTRL.fb_concat; *dst++ = 0xFF; addr += bpp; } addr += modulus * bpp; } break; case FBDE_888: // 888 RGB for (int y = 0; y < height; y++) { for (int i = 0; i < width; i += 4) { u32 src = pvr_read_area1(addr); *dst++ = src >> 16; *dst++ = src >> 8; *dst++ = src; *dst++ = 0xFF; addr += 4; if (i + 1 >= width) break; u32 src2 = pvr_read_area1(addr); *dst++ = src2 >> 8; *dst++ = src2; *dst++ = src >> 24; *dst++ = 0xFF; addr += 4; if (i + 2 >= width) break; u32 src3 = pvr_read_area1(addr); *dst++ = src3; *dst++ = src2 >> 24; *dst++ = src2 >> 16; *dst++ = 0xFF; addr += 4; if (i + 3 >= width) break; *dst++ = src3 >> 24; *dst++ = src3 >> 16; *dst++ = src3 >> 8; *dst++ = 0xFF; } addr += modulus * bpp; } break; case FBDE_C888: // 0888 RGB for (int y = 0; y < height; y++) { for (int i = 0; i < width; i++) { u32 src = pvr_read_area1(addr); *dst++ = src >> 16; *dst++ = src >> 8; *dst++ = src; *dst++ = 0xFF; addr += bpp; } addr += modulus * bpp; } break; } } static void rend_text_invl(vram_block* bl) { BaseTextureCacheData* tcd = (BaseTextureCacheData*)bl->userdata; tcd->dirty = FrameCount; tcd->lock_block = nullptr; libCore_vramlock_Unlock_block_wb(bl); }