mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2024-11-14 21:37:42 -07:00
fixes and tex cache support for OGL renderer
includes xbrz upscaling, currently hardcoded to x2
This commit is contained in:
parent
ed61867dec
commit
c70519070b
@ -5,7 +5,7 @@ if (POLICY CMP0076)
|
||||
cmake_policy(SET CMP0076 NEW)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
project(melonDS)
|
||||
|
@ -28,6 +28,7 @@ add_library(core STATIC
|
||||
|
||||
xxhash/xxhash.c
|
||||
stb/stb.cpp
|
||||
xbrz/xbrz.cpp
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
@ -602,7 +602,7 @@ void MapVRAM_FG(u32 bank, u8 cnt)
|
||||
VRAMPtr_ABG[base] = GetUniqueBankPtr(VRAMMap_ABG[base], base << 14);
|
||||
VRAMPtr_ABG[base + 2] = GetUniqueBankPtr(VRAMMap_ABG[base + 2], (base + 2) << 14);
|
||||
}
|
||||
LCDCDirty[bank][0] = 0xFFFFFFFF;
|
||||
LCDCDirty[bank][0] = 0xFFFF;
|
||||
break;
|
||||
|
||||
case 2: // AOBJ
|
||||
@ -613,7 +613,7 @@ void MapVRAM_FG(u32 bank, u8 cnt)
|
||||
VRAMPtr_AOBJ[base] = GetUniqueBankPtr(VRAMMap_AOBJ[base], base << 14);
|
||||
VRAMPtr_AOBJ[base + 2] = GetUniqueBankPtr(VRAMMap_AOBJ[base + 2], (base + 2) << 14);
|
||||
}
|
||||
LCDCDirty[bank][0] = 0xFFFFFFFF;
|
||||
LCDCDirty[bank][0] = 0xFFFF;
|
||||
break;
|
||||
|
||||
case 3: // texture palette
|
||||
|
@ -224,7 +224,7 @@ void WriteVRAM_LCDC(u32 addr, T val)
|
||||
if (VRAMMap_LCDC & (1<<bank))
|
||||
{
|
||||
*(T*)&VRAM[bank][addr] = val;
|
||||
LCDCDirty[bank][addr >> 16] |= 1 << ((addr >> 10) & 0x3F);
|
||||
LCDCDirty[bank][addr >> 16] |= 1ULL << ((addr >> 10) & 0x3F);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,10 +132,6 @@ namespace TexCache
|
||||
|
||||
typedef u64 ExternalTexHandle;
|
||||
|
||||
typedef u32* (*AllocTextureFunc)(ExternalTexHandle* handle, u32 width, u32 height);
|
||||
typedef void (*FreeTextureFunc)(ExternalTexHandle handle, u32 width, u32 height);
|
||||
typedef void (*FinaliseTextureFunc)(ExternalTexHandle handle, u32 width, u32 height);
|
||||
|
||||
enum
|
||||
{
|
||||
outputFmt_RGB6A5,
|
||||
@ -183,6 +179,10 @@ bool Init();
|
||||
void DeInit();
|
||||
void Reset();
|
||||
|
||||
u32* AllocateTexture(TexCache::ExternalTexHandle* handle, u32 width, u32 height);
|
||||
void FreeTexture(TexCache::ExternalTexHandle handle, u32 width, u32 height);
|
||||
void FinaliseTexture(TexCache::ExternalTexHandle handle, u32 width, u32 height);
|
||||
|
||||
void UpdateDisplaySettings();
|
||||
|
||||
void RenderFrame();
|
||||
|
@ -24,6 +24,10 @@
|
||||
#include "OpenGLSupport.h"
|
||||
#include "GPU3D_OpenGL_shaders.h"
|
||||
|
||||
#include "xbrz/xbrz.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace GPU3D
|
||||
{
|
||||
namespace GLRenderer
|
||||
@ -80,6 +84,8 @@ typedef struct
|
||||
|
||||
u32 RenderKey;
|
||||
|
||||
GLuint TextureID;
|
||||
|
||||
} RendererPolygon;
|
||||
|
||||
RendererPolygon PolygonList[2048];
|
||||
@ -107,9 +113,6 @@ u32 NumVertices;
|
||||
GLuint VertexArrayID;
|
||||
u16 IndexBuffer[2048 * 40];
|
||||
|
||||
GLuint TexMemID;
|
||||
GLuint TexPalMemID;
|
||||
|
||||
int ScaleFactor;
|
||||
bool Antialias;
|
||||
int ScreenW, ScreenH;
|
||||
@ -119,7 +122,108 @@ int FrontBuffer;
|
||||
GLuint FramebufferID[4], PixelbufferID;
|
||||
u32 Framebuffer[256*192];
|
||||
|
||||
struct TextureAllocator
|
||||
{
|
||||
u32 Length = 0;
|
||||
GLuint TextureID;
|
||||
u32 FreeEntriesLeft = 0;
|
||||
u64 FreeEntries[8];
|
||||
u32* Pixels;
|
||||
};
|
||||
TextureAllocator TextureMem[8][8];
|
||||
|
||||
inline TextureAllocator& GetTextureAllocator(u32 width, u32 height)
|
||||
{
|
||||
return TextureMem[__builtin_ctz(width) - 3][__builtin_ctz(height) - 3];
|
||||
}
|
||||
|
||||
u32 ConversionBuffer[1024*1024];
|
||||
|
||||
u32* AllocateTexture(TexCache::ExternalTexHandle* handle, u32 width, u32 height)
|
||||
{
|
||||
TextureAllocator& allocator = GetTextureAllocator(width, height);
|
||||
//printf("allocating texture %d %d\n", width, height);
|
||||
|
||||
u32 scaledWidth = width * 2;
|
||||
u32 scaledHeight = height * 2;
|
||||
|
||||
if (allocator.FreeEntriesLeft == 0)
|
||||
{
|
||||
u32 newLength = (allocator.Length * 3) / 2 + 8;
|
||||
|
||||
if (newLength >= 64*8)
|
||||
abort();
|
||||
|
||||
if (allocator.Length == 0)
|
||||
{
|
||||
printf("created new texture\n");
|
||||
glGenTextures(1, &allocator.TextureID);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, allocator.TextureID);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
|
||||
}
|
||||
else
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, allocator.TextureID);
|
||||
|
||||
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, scaledWidth, scaledHeight, newLength, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
u32* newPixels = new u32[scaledWidth * scaledHeight * newLength];
|
||||
if (allocator.Length)
|
||||
{
|
||||
// copy old data
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, scaledWidth, scaledHeight, allocator.Length, GL_RGBA, GL_UNSIGNED_BYTE, allocator.Pixels);
|
||||
|
||||
memcpy(newPixels, allocator.Pixels, allocator.Length * scaledWidth * scaledHeight * 4);
|
||||
delete[] allocator.Pixels;
|
||||
}
|
||||
allocator.Pixels = newPixels;
|
||||
|
||||
allocator.FreeEntriesLeft = newLength - allocator.Length;
|
||||
allocator.Length = newLength;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (allocator.Length + 0x3F & ~0x3F) >> 6; i++)
|
||||
{
|
||||
if (allocator.FreeEntries[i] != 0xFFFFFFFFFFFFFFFF)
|
||||
{
|
||||
allocator.FreeEntriesLeft--;
|
||||
u64 freeIdx = __builtin_ctzll(~allocator.FreeEntries[i]);
|
||||
allocator.FreeEntries[i] |= 1ULL << freeIdx;
|
||||
*handle = i * 64 + freeIdx;
|
||||
|
||||
//return &allocator.Pixels[*handle * scaledWidth * scaledHeight];
|
||||
return ConversionBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
// should never happen, hopefully...
|
||||
abort();
|
||||
}
|
||||
|
||||
void FreeTexture(TexCache::ExternalTexHandle handle, u32 width, u32 height)
|
||||
{
|
||||
TextureAllocator& allocator = GetTextureAllocator(width, height);
|
||||
|
||||
allocator.FreeEntriesLeft++;
|
||||
allocator.FreeEntries[handle >> 6] &= ~(1 << (handle & 0x3F));
|
||||
}
|
||||
|
||||
void FinaliseTexture(TexCache::ExternalTexHandle handle, u32 width, u32 height)
|
||||
{
|
||||
TextureAllocator& allocator = GetTextureAllocator(width, height);
|
||||
|
||||
u32 scaledWidth = width * 2;
|
||||
u32 scaledHeight = height * 2;
|
||||
|
||||
xbrz::scale(2, ConversionBuffer, &allocator.Pixels[handle * scaledWidth * scaledHeight], width, height, xbrz::ColorFormat::ARGB);
|
||||
|
||||
// could still be improved
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, allocator.TextureID);
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, handle, scaledWidth, scaledHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, &allocator.Pixels[handle * scaledWidth * scaledHeight]);
|
||||
}
|
||||
|
||||
bool BuildRenderShader(u32 flags, const char* vs, const char* fs)
|
||||
{
|
||||
@ -166,10 +270,8 @@ bool BuildRenderShader(u32 flags, const char* vs, const char* fs)
|
||||
|
||||
glUseProgram(prog);
|
||||
|
||||
uni_id = glGetUniformLocation(prog, "TexMem");
|
||||
uni_id = glGetUniformLocation(prog, "Textures");
|
||||
glUniform1i(uni_id, 0);
|
||||
uni_id = glGetUniformLocation(prog, "TexPalMem");
|
||||
glUniform1i(uni_id, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -201,7 +303,6 @@ bool Init()
|
||||
glDepthRange(0, 1);
|
||||
glClearDepth(1.0);
|
||||
|
||||
|
||||
if (!OpenGL_BuildShaderProgram(kClearVS, kClearFS, ClearShaderPlain, "ClearShader"))
|
||||
return false;
|
||||
|
||||
@ -353,32 +454,11 @@ bool Init()
|
||||
|
||||
glGenBuffers(1, &PixelbufferID);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glGenTextures(1, &TexMemID);
|
||||
glBindTexture(GL_TEXTURE_2D, TexMemID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8UI, 1024, 512, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL);
|
||||
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glGenTextures(1, &TexPalMemID);
|
||||
glBindTexture(GL_TEXTURE_2D, TexPalMemID);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, 1024, 48, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeInit()
|
||||
{
|
||||
glDeleteTextures(1, &TexMemID);
|
||||
glDeleteTextures(1, &TexPalMemID);
|
||||
|
||||
glDeleteFramebuffers(4, &FramebufferID[0]);
|
||||
glDeleteTextures(8, &FramebufferTex[0]);
|
||||
|
||||
@ -539,6 +619,17 @@ void BuildPolygons(RendererPolygon* polygons, int npolys)
|
||||
RendererPolygon* rp = &polygons[i];
|
||||
Polygon* poly = rp->PolyData;
|
||||
|
||||
TexCache::ExternalTexHandle texture = UINT64_MAX;
|
||||
if (poly->TexParam & 0x1C000000 && !poly->IsShadowMask)
|
||||
{
|
||||
texture = TexCache::GetTexture<TexCache::outputFmt_RGBA8>(poly->TexParam, poly->TexPalette);
|
||||
|
||||
u32 width = (poly->TexParam >> 20) & 0x7;
|
||||
u32 height = (poly->TexParam >> 23) & 0x7;
|
||||
|
||||
rp->TextureID = TextureMem[width][height].TextureID;
|
||||
}
|
||||
|
||||
rp->Indices = iptr;
|
||||
rp->NumIndices = 0;
|
||||
|
||||
@ -599,7 +690,7 @@ void BuildPolygons(RendererPolygon* polygons, int npolys)
|
||||
|
||||
*vptr++ = vtxattr | (zshift << 16);
|
||||
*vptr++ = poly->TexParam;
|
||||
*vptr++ = poly->TexPalette;
|
||||
*vptr++ = texture;
|
||||
|
||||
*iptr++ = vidx;
|
||||
rp->NumIndices++;
|
||||
@ -648,7 +739,7 @@ void BuildPolygons(RendererPolygon* polygons, int npolys)
|
||||
|
||||
*vptr++ = vtxattr | (zshift << 16);
|
||||
*vptr++ = poly->TexParam;
|
||||
*vptr++ = poly->TexPalette;
|
||||
*vptr++ = texture;
|
||||
|
||||
if (j >= 2)
|
||||
{
|
||||
@ -686,6 +777,8 @@ void RenderSinglePolygon(int i)
|
||||
{
|
||||
RendererPolygon* rp = &PolygonList[i];
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, rp->TextureID);
|
||||
|
||||
glDrawElements(rp->PrimType, rp->NumIndices, GL_UNSIGNED_SHORT, rp->Indices);
|
||||
}
|
||||
|
||||
@ -697,17 +790,38 @@ int RenderPolygonBatch(int i)
|
||||
int numpolys = 0;
|
||||
u32 numindices = 0;
|
||||
|
||||
for (int iend = i; iend < NumFinalPolys; iend++)
|
||||
GLuint texture;
|
||||
bool textureChange;
|
||||
|
||||
do
|
||||
{
|
||||
RendererPolygon* cur_rp = &PolygonList[iend];
|
||||
textureChange = false;
|
||||
|
||||
rp = &PolygonList[i];
|
||||
texture = rp->TextureID;
|
||||
|
||||
for (; i < NumFinalPolys; i++)
|
||||
{
|
||||
RendererPolygon* cur_rp = &PolygonList[i];
|
||||
if (cur_rp->PrimType != primtype) break;
|
||||
if (cur_rp->RenderKey != key) break;
|
||||
if (cur_rp->TextureID != texture)
|
||||
{
|
||||
textureChange = true;
|
||||
break;
|
||||
}
|
||||
|
||||
numpolys++;
|
||||
numindices += cur_rp->NumIndices;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
|
||||
|
||||
glDrawElements(primtype, numindices, GL_UNSIGNED_SHORT, rp->Indices);
|
||||
|
||||
numindices = 0;
|
||||
} while (textureChange);
|
||||
|
||||
return numpolys;
|
||||
}
|
||||
|
||||
@ -1111,38 +1225,7 @@ void RenderFrame()
|
||||
if (unibuf) memcpy(unibuf, &ShaderConfig, sizeof(ShaderConfig));
|
||||
glUnmapBuffer(GL_UNIFORM_BUFFER);
|
||||
|
||||
// SUCKY!!!!!!!!!!!!!!!!!!
|
||||
// TODO: detect when VRAM blocks are modified!
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, TexMemID);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
u32 mask = GPU::VRAMMap_Texture[i];
|
||||
u8* vram;
|
||||
if (!mask) continue;
|
||||
else if (mask & (1<<0)) vram = GPU::VRAM_A;
|
||||
else if (mask & (1<<1)) vram = GPU::VRAM_B;
|
||||
else if (mask & (1<<2)) vram = GPU::VRAM_C;
|
||||
else if (mask & (1<<3)) vram = GPU::VRAM_D;
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, i*128, 1024, 128, GL_RED_INTEGER, GL_UNSIGNED_BYTE, vram);
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, TexPalMemID);
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
// 6 x 16K chunks
|
||||
u32 mask = GPU::VRAMMap_TexPal[i];
|
||||
u8* vram;
|
||||
if (!mask) continue;
|
||||
else if (mask & (1<<4)) vram = &GPU::VRAM_E[(i&3)*0x4000];
|
||||
else if (mask & (1<<5)) vram = GPU::VRAM_F;
|
||||
else if (mask & (1<<6)) vram = GPU::VRAM_G;
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, i*8, 1024, 8, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, vram);
|
||||
}
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
@ -1188,6 +1271,8 @@ void RenderFrame()
|
||||
glDrawArrays(GL_TRIANGLES, 0, 2*3);
|
||||
}
|
||||
|
||||
TexCache::UpdateTextures();
|
||||
|
||||
if (RenderNumPolygons)
|
||||
{
|
||||
// render shit here
|
||||
|
@ -241,9 +241,6 @@ flat out ivec3 fPolygonAttr;
|
||||
|
||||
const char* kRenderFSCommon = R"(
|
||||
|
||||
uniform usampler2D TexMem;
|
||||
uniform sampler2D TexPalMem;
|
||||
|
||||
layout(std140) uniform uConfig
|
||||
{
|
||||
vec2 uScreenSize;
|
||||
@ -260,334 +257,23 @@ smooth in vec4 fColor;
|
||||
smooth in vec2 fTexcoord;
|
||||
flat in ivec3 fPolygonAttr;
|
||||
|
||||
uniform sampler2DArray Textures;
|
||||
|
||||
out vec4 oColor;
|
||||
out vec4 oAttr;
|
||||
|
||||
int TexcoordWrap(int c, int maxc, int mode)
|
||||
float TexcoordWrap(float c, int mode)
|
||||
{
|
||||
if ((mode & (1<<0)) != 0)
|
||||
{
|
||||
if ((mode & (1<<2)) != 0 && (c & maxc) != 0)
|
||||
return (maxc-1) - (c & (maxc-1));
|
||||
if ((mode & (1<<2)) == 0)
|
||||
return fract(c);
|
||||
else
|
||||
return (c & (maxc-1));
|
||||
// mirrored repeat is the most complex one, so we let the hardware handle it
|
||||
return c;
|
||||
}
|
||||
else
|
||||
return clamp(c, 0, maxc-1);
|
||||
}
|
||||
|
||||
vec4 TextureFetch_A3I5(ivec2 addr, ivec4 st, int wrapmode)
|
||||
{
|
||||
st.x = TexcoordWrap(st.x, st.z, wrapmode>>0);
|
||||
st.y = TexcoordWrap(st.y, st.w, wrapmode>>1);
|
||||
|
||||
addr.x += ((st.y * st.z) + st.x);
|
||||
ivec4 pixel = ivec4(texelFetch(TexMem, ivec2(addr.x&0x3FF, addr.x>>10), 0));
|
||||
|
||||
pixel.a = (pixel.r & 0xE0);
|
||||
pixel.a = (pixel.a >> 3) + (pixel.a >> 6);
|
||||
pixel.r &= 0x1F;
|
||||
|
||||
addr.y = (addr.y << 3) + pixel.r;
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
|
||||
return vec4(color.rgb, float(pixel.a)/31.0);
|
||||
}
|
||||
|
||||
vec4 TextureFetch_I2(ivec2 addr, ivec4 st, int wrapmode, float alpha0)
|
||||
{
|
||||
st.x = TexcoordWrap(st.x, st.z, wrapmode>>0);
|
||||
st.y = TexcoordWrap(st.y, st.w, wrapmode>>1);
|
||||
|
||||
addr.x += ((st.y * st.z) + st.x) >> 2;
|
||||
ivec4 pixel = ivec4(texelFetch(TexMem, ivec2(addr.x&0x3FF, addr.x>>10), 0));
|
||||
pixel.r >>= (2 * (st.x & 3));
|
||||
pixel.r &= 0x03;
|
||||
|
||||
addr.y = (addr.y << 2) + pixel.r;
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
|
||||
return vec4(color.rgb, (pixel.r>0)?1:alpha0);
|
||||
}
|
||||
|
||||
vec4 TextureFetch_I4(ivec2 addr, ivec4 st, int wrapmode, float alpha0)
|
||||
{
|
||||
st.x = TexcoordWrap(st.x, st.z, wrapmode>>0);
|
||||
st.y = TexcoordWrap(st.y, st.w, wrapmode>>1);
|
||||
|
||||
addr.x += ((st.y * st.z) + st.x) >> 1;
|
||||
ivec4 pixel = ivec4(texelFetch(TexMem, ivec2(addr.x&0x3FF, addr.x>>10), 0));
|
||||
if ((st.x & 1) != 0) pixel.r >>= 4;
|
||||
else pixel.r &= 0x0F;
|
||||
|
||||
addr.y = (addr.y << 3) + pixel.r;
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
|
||||
return vec4(color.rgb, (pixel.r>0)?1:alpha0);
|
||||
}
|
||||
|
||||
vec4 TextureFetch_I8(ivec2 addr, ivec4 st, int wrapmode, float alpha0)
|
||||
{
|
||||
st.x = TexcoordWrap(st.x, st.z, wrapmode>>0);
|
||||
st.y = TexcoordWrap(st.y, st.w, wrapmode>>1);
|
||||
|
||||
addr.x += ((st.y * st.z) + st.x);
|
||||
ivec4 pixel = ivec4(texelFetch(TexMem, ivec2(addr.x&0x3FF, addr.x>>10), 0));
|
||||
|
||||
addr.y = (addr.y << 3) + pixel.r;
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
|
||||
return vec4(color.rgb, (pixel.r>0)?1:alpha0);
|
||||
}
|
||||
|
||||
vec4 TextureFetch_Compressed(ivec2 addr, ivec4 st, int wrapmode)
|
||||
{
|
||||
st.x = TexcoordWrap(st.x, st.z, wrapmode>>0);
|
||||
st.y = TexcoordWrap(st.y, st.w, wrapmode>>1);
|
||||
|
||||
addr.x += ((st.y & 0x3FC) * (st.z>>2)) + (st.x & 0x3FC) + (st.y & 0x3);
|
||||
ivec4 p = ivec4(texelFetch(TexMem, ivec2(addr.x&0x3FF, addr.x>>10), 0));
|
||||
int val = (p.r >> (2 * (st.x & 0x3))) & 0x3;
|
||||
|
||||
int slot1addr = 0x20000 + ((addr.x & 0x1FFFC) >> 1);
|
||||
if (addr.x >= 0x40000) slot1addr += 0x10000;
|
||||
|
||||
int palinfo;
|
||||
p = ivec4(texelFetch(TexMem, ivec2(slot1addr&0x3FF, slot1addr>>10), 0));
|
||||
palinfo = p.r;
|
||||
slot1addr++;
|
||||
p = ivec4(texelFetch(TexMem, ivec2(slot1addr&0x3FF, slot1addr>>10), 0));
|
||||
palinfo |= (p.r << 8);
|
||||
|
||||
addr.y = (addr.y << 3) + ((palinfo & 0x3FFF) << 1);
|
||||
palinfo >>= 14;
|
||||
|
||||
if (val == 0)
|
||||
{
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
return vec4(color.rgb, 1.0);
|
||||
}
|
||||
else if (val == 1)
|
||||
{
|
||||
addr.y++;
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
return vec4(color.rgb, 1.0);
|
||||
}
|
||||
else if (val == 2)
|
||||
{
|
||||
if (palinfo == 1)
|
||||
{
|
||||
vec4 color0 = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
addr.y++;
|
||||
vec4 color1 = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
return vec4((color0.rgb + color1.rgb) / 2.0, 1.0);
|
||||
}
|
||||
else if (palinfo == 3)
|
||||
{
|
||||
vec4 color0 = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
addr.y++;
|
||||
vec4 color1 = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
return vec4((color0.rgb*5.0 + color1.rgb*3.0) / 8.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
addr.y += 2;
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
return vec4(color.rgb, 1.0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (palinfo == 2)
|
||||
{
|
||||
addr.y += 3;
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
return vec4(color.rgb, 1.0);
|
||||
}
|
||||
else if (palinfo == 3)
|
||||
{
|
||||
vec4 color0 = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
addr.y++;
|
||||
vec4 color1 = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
return vec4((color0.rgb*3.0 + color1.rgb*5.0) / 8.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return vec4(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec4 TextureFetch_A5I3(ivec2 addr, ivec4 st, int wrapmode)
|
||||
{
|
||||
st.x = TexcoordWrap(st.x, st.z, wrapmode>>0);
|
||||
st.y = TexcoordWrap(st.y, st.w, wrapmode>>1);
|
||||
|
||||
addr.x += ((st.y * st.z) + st.x);
|
||||
ivec4 pixel = ivec4(texelFetch(TexMem, ivec2(addr.x&0x3FF, addr.x>>10), 0));
|
||||
|
||||
pixel.a = (pixel.r & 0xF8) >> 3;
|
||||
pixel.r &= 0x07;
|
||||
|
||||
addr.y = (addr.y << 3) + pixel.r;
|
||||
vec4 color = texelFetch(TexPalMem, ivec2(addr.y&0x3FF, addr.y>>10), 0);
|
||||
|
||||
return vec4(color.rgb, float(pixel.a)/31.0);
|
||||
}
|
||||
|
||||
vec4 TextureFetch_Direct(ivec2 addr, ivec4 st, int wrapmode)
|
||||
{
|
||||
st.x = TexcoordWrap(st.x, st.z, wrapmode>>0);
|
||||
st.y = TexcoordWrap(st.y, st.w, wrapmode>>1);
|
||||
|
||||
addr.x += ((st.y * st.z) + st.x) << 1;
|
||||
ivec4 pixelL = ivec4(texelFetch(TexMem, ivec2(addr.x&0x3FF, addr.x>>10), 0));
|
||||
addr.x++;
|
||||
ivec4 pixelH = ivec4(texelFetch(TexMem, ivec2(addr.x&0x3FF, addr.x>>10), 0));
|
||||
|
||||
vec4 color;
|
||||
color.r = float(pixelL.r & 0x1F) / 31.0;
|
||||
color.g = float((pixelL.r >> 5) | ((pixelH.r & 0x03) << 3)) / 31.0;
|
||||
color.b = float((pixelH.r & 0x7C) >> 2) / 31.0;
|
||||
color.a = float(pixelH.r >> 7);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
vec4 TextureLookup_Nearest(vec2 st)
|
||||
{
|
||||
int attr = int(fPolygonAttr.y);
|
||||
int paladdr = int(fPolygonAttr.z);
|
||||
|
||||
float alpha0;
|
||||
if ((attr & (1<<29)) != 0) alpha0 = 0.0;
|
||||
else alpha0 = 1.0;
|
||||
|
||||
int tw = 8 << ((attr >> 20) & 0x7);
|
||||
int th = 8 << ((attr >> 23) & 0x7);
|
||||
ivec4 st_full = ivec4(ivec2(st), tw, th);
|
||||
|
||||
ivec2 vramaddr = ivec2((attr & 0xFFFF) << 3, paladdr);
|
||||
int wrapmode = (attr >> 16);
|
||||
|
||||
int type = (attr >> 26) & 0x7;
|
||||
if (type == 5) return TextureFetch_Compressed(vramaddr, st_full, wrapmode);
|
||||
else if (type == 2) return TextureFetch_I2 (vramaddr, st_full, wrapmode, alpha0);
|
||||
else if (type == 3) return TextureFetch_I4 (vramaddr, st_full, wrapmode, alpha0);
|
||||
else if (type == 4) return TextureFetch_I8 (vramaddr, st_full, wrapmode, alpha0);
|
||||
else if (type == 1) return TextureFetch_A3I5 (vramaddr, st_full, wrapmode);
|
||||
else if (type == 6) return TextureFetch_A5I3 (vramaddr, st_full, wrapmode);
|
||||
else return TextureFetch_Direct (vramaddr, st_full, wrapmode);
|
||||
}
|
||||
|
||||
vec4 TextureLookup_Linear(vec2 texcoord)
|
||||
{
|
||||
ivec2 intpart = ivec2(texcoord);
|
||||
vec2 fracpart = fract(texcoord);
|
||||
|
||||
int attr = int(fPolygonAttr.y);
|
||||
int paladdr = int(fPolygonAttr.z);
|
||||
|
||||
float alpha0;
|
||||
if ((attr & (1<<29)) != 0) alpha0 = 0.0;
|
||||
else alpha0 = 1.0;
|
||||
|
||||
int tw = 8 << ((attr >> 20) & 0x7);
|
||||
int th = 8 << ((attr >> 23) & 0x7);
|
||||
ivec4 st_full = ivec4(intpart, tw, th);
|
||||
|
||||
ivec2 vramaddr = ivec2((attr & 0xFFFF) << 3, paladdr);
|
||||
int wrapmode = (attr >> 16);
|
||||
|
||||
vec4 A, B, C, D;
|
||||
int type = (attr >> 26) & 0x7;
|
||||
if (type == 5)
|
||||
{
|
||||
A = TextureFetch_Compressed(vramaddr, st_full , wrapmode);
|
||||
B = TextureFetch_Compressed(vramaddr, st_full + ivec4(1,0,0,0), wrapmode);
|
||||
C = TextureFetch_Compressed(vramaddr, st_full + ivec4(0,1,0,0), wrapmode);
|
||||
D = TextureFetch_Compressed(vramaddr, st_full + ivec4(1,1,0,0), wrapmode);
|
||||
}
|
||||
else if (type == 2)
|
||||
{
|
||||
A = TextureFetch_I2(vramaddr, st_full , wrapmode, alpha0);
|
||||
B = TextureFetch_I2(vramaddr, st_full + ivec4(1,0,0,0), wrapmode, alpha0);
|
||||
C = TextureFetch_I2(vramaddr, st_full + ivec4(0,1,0,0), wrapmode, alpha0);
|
||||
D = TextureFetch_I2(vramaddr, st_full + ivec4(1,1,0,0), wrapmode, alpha0);
|
||||
}
|
||||
else if (type == 3)
|
||||
{
|
||||
A = TextureFetch_I4(vramaddr, st_full , wrapmode, alpha0);
|
||||
B = TextureFetch_I4(vramaddr, st_full + ivec4(1,0,0,0), wrapmode, alpha0);
|
||||
C = TextureFetch_I4(vramaddr, st_full + ivec4(0,1,0,0), wrapmode, alpha0);
|
||||
D = TextureFetch_I4(vramaddr, st_full + ivec4(1,1,0,0), wrapmode, alpha0);
|
||||
}
|
||||
else if (type == 4)
|
||||
{
|
||||
A = TextureFetch_I8(vramaddr, st_full , wrapmode, alpha0);
|
||||
B = TextureFetch_I8(vramaddr, st_full + ivec4(1,0,0,0), wrapmode, alpha0);
|
||||
C = TextureFetch_I8(vramaddr, st_full + ivec4(0,1,0,0), wrapmode, alpha0);
|
||||
D = TextureFetch_I8(vramaddr, st_full + ivec4(1,1,0,0), wrapmode, alpha0);
|
||||
}
|
||||
else if (type == 1)
|
||||
{
|
||||
A = TextureFetch_A3I5(vramaddr, st_full , wrapmode);
|
||||
B = TextureFetch_A3I5(vramaddr, st_full + ivec4(1,0,0,0), wrapmode);
|
||||
C = TextureFetch_A3I5(vramaddr, st_full + ivec4(0,1,0,0), wrapmode);
|
||||
D = TextureFetch_A3I5(vramaddr, st_full + ivec4(1,1,0,0), wrapmode);
|
||||
}
|
||||
else if (type == 6)
|
||||
{
|
||||
A = TextureFetch_A5I3(vramaddr, st_full , wrapmode);
|
||||
B = TextureFetch_A5I3(vramaddr, st_full + ivec4(1,0,0,0), wrapmode);
|
||||
C = TextureFetch_A5I3(vramaddr, st_full + ivec4(0,1,0,0), wrapmode);
|
||||
D = TextureFetch_A5I3(vramaddr, st_full + ivec4(1,1,0,0), wrapmode);
|
||||
}
|
||||
else
|
||||
{
|
||||
A = TextureFetch_Direct(vramaddr, st_full , wrapmode);
|
||||
B = TextureFetch_Direct(vramaddr, st_full + ivec4(1,0,0,0), wrapmode);
|
||||
C = TextureFetch_Direct(vramaddr, st_full + ivec4(0,1,0,0), wrapmode);
|
||||
D = TextureFetch_Direct(vramaddr, st_full + ivec4(1,1,0,0), wrapmode);
|
||||
}
|
||||
|
||||
float fx = fracpart.x;
|
||||
vec4 AB;
|
||||
if (A.a < (0.5/31.0) && B.a < (0.5/31.0))
|
||||
AB = vec4(0);
|
||||
else
|
||||
{
|
||||
//if (A.a < (0.5/31.0) || B.a < (0.5/31.0))
|
||||
// fx = step(0.5, fx);
|
||||
|
||||
AB = mix(A, B, fx);
|
||||
}
|
||||
|
||||
fx = fracpart.x;
|
||||
vec4 CD;
|
||||
if (C.a < (0.5/31.0) && D.a < (0.5/31.0))
|
||||
CD = vec4(0);
|
||||
else
|
||||
{
|
||||
//if (C.a < (0.5/31.0) || D.a < (0.5/31.0))
|
||||
// fx = step(0.5, fx);
|
||||
|
||||
CD = mix(C, D, fx);
|
||||
}
|
||||
|
||||
fx = fracpart.y;
|
||||
vec4 ret;
|
||||
if (AB.a < (0.5/31.0) && CD.a < (0.5/31.0))
|
||||
ret = vec4(0);
|
||||
else
|
||||
{
|
||||
//if (AB.a < (0.5/31.0) || CD.a < (0.5/31.0))
|
||||
// fx = step(0.5, fx);
|
||||
|
||||
ret = mix(AB, CD, fx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return clamp(c, 0, 1);
|
||||
}
|
||||
|
||||
vec4 FinalColor()
|
||||
@ -618,8 +304,12 @@ vec4 FinalColor()
|
||||
}
|
||||
else
|
||||
{
|
||||
vec4 tcol = TextureLookup_Nearest(fTexcoord);
|
||||
//vec4 tcol = TextureLookup_Linear(fTexcoord);
|
||||
int wrapmode = (fPolygonAttr.y >> 16);
|
||||
|
||||
vec2 st = vec2(
|
||||
TexcoordWrap(fTexcoord.x, wrapmode>>0),
|
||||
TexcoordWrap(fTexcoord.y, wrapmode>>1));
|
||||
vec4 tcol = texture(Textures, vec3(st, float(fPolygonAttr.z)));
|
||||
|
||||
if ((blendmode & 1) != 0)
|
||||
{
|
||||
@ -662,7 +352,10 @@ void main()
|
||||
fpos.xyz *= fpos.w;
|
||||
|
||||
fColor = vec4(vColor) / vec4(255.0,255.0,255.0,31.0);
|
||||
fTexcoord = vec2(vTexcoord) / 16.0;
|
||||
vec2 texSize = vec2(
|
||||
float(8 << ((vPolygonAttr.y >> 20) & 0x7)),
|
||||
float(8 << ((vPolygonAttr.y >> 23) & 0x7)));
|
||||
fTexcoord = vec2(vTexcoord) / (texSize * 16.0);
|
||||
fPolygonAttr = vPolygonAttr;
|
||||
|
||||
gl_Position = fpos;
|
||||
@ -685,7 +378,11 @@ void main()
|
||||
fpos.xy *= fpos.w;
|
||||
|
||||
fColor = vec4(vColor) / vec4(255.0,255.0,255.0,31.0);
|
||||
fTexcoord = vec2(vTexcoord) / 16.0;
|
||||
|
||||
vec2 texSize = vec2(
|
||||
float(8 << ((vPolygonAttr.y >> 20) & 0x7)),
|
||||
float(8 << ((vPolygonAttr.y >> 23) & 0x7)));
|
||||
fTexcoord = vec2(vTexcoord) / (texSize * 16.0);
|
||||
fPolygonAttr = vPolygonAttr;
|
||||
|
||||
gl_Position = fpos;
|
||||
|
@ -1138,7 +1138,7 @@ void SetupPolygonRightEdge(RendererPolygon* rp, s32 y)
|
||||
|
||||
void SetupPolygon(RendererPolygon* rp, Polygon* polygon, RendererPolygon* lastRp)
|
||||
{
|
||||
if (polygon->TexParam & 0x1C000000)
|
||||
if (polygon->TexParam & 0x1C000000 && !polygon->IsShadowMask)
|
||||
{
|
||||
if (lastRp && lastRp->PolyData->TexParam == polygon->TexParam
|
||||
&& lastRp->PolyData->TexPalette == polygon->TexPalette)
|
||||
|
@ -300,7 +300,7 @@ void EnsurePaletteCoherent(u64* mask)
|
||||
{
|
||||
updatePalette = true;
|
||||
int idx = __builtin_ctzll(updateField);
|
||||
u32 map = GPU::VRAMMap_TexPal[idx >> 4 + i * 4];
|
||||
u32 map = GPU::VRAMMap_TexPal[(idx >> 4) + i * 4];
|
||||
if (map && (map & (map - 1)) == 0)
|
||||
{
|
||||
u32 bank = __builtin_ctz(map);
|
||||
@ -363,20 +363,20 @@ void UpdateTextures()
|
||||
else
|
||||
{
|
||||
// E
|
||||
if (PaletteMap[i] & (1<<3))
|
||||
if (PaletteMap[i] & (1<<4))
|
||||
{
|
||||
PaletteDirty[i >> 2] |= GPU::LCDCDirty[3][0];
|
||||
PaletteCacheStatus[i >> 2] &= ~GPU::LCDCDirty[3][0];
|
||||
GPU::LCDCDirty[3][0] = 0;
|
||||
PaletteDirty[i >> 2] |= GPU::LCDCDirty[4][0];
|
||||
PaletteCacheStatus[i >> 2] &= ~GPU::LCDCDirty[4][0];
|
||||
GPU::LCDCDirty[4][0] = 0;
|
||||
}
|
||||
// FG
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
if (PaletteMap[i] & (1<<(4+j)))
|
||||
if (PaletteMap[i] & (1<<(5+j)))
|
||||
{
|
||||
PaletteDirty[i >> 2] |= GPU::LCDCDirty[4+j][0] << (i & 0x3) * 16;
|
||||
PaletteCacheStatus[i >> 2] &= ~(GPU::LCDCDirty[4+j][0] << (i & 0x3) * 16);
|
||||
GPU::LCDCDirty[4+j][0] = 0;
|
||||
PaletteDirty[i >> 2] |= GPU::LCDCDirty[5+j][0] << (i & 0x3) * 16;
|
||||
PaletteCacheStatus[i >> 2] &= ~((GPU::LCDCDirty[5+j][0] & 0xFFFF) << (i & 0x3) * 16);
|
||||
GPU::LCDCDirty[5+j][0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -405,6 +405,8 @@ void UpdateTextures()
|
||||
|
||||
if (GPU3D::Renderer == 0)
|
||||
SoftRenderer::FreeTexture(it->second.Handle, width, height);
|
||||
else
|
||||
GLRenderer::FreeTexture(it->second.Handle, width, height);
|
||||
|
||||
it = TextureCache.erase(it);
|
||||
}
|
||||
@ -462,7 +464,7 @@ ExternalTexHandle GetTexture(u32 texParam, u32 palBase)
|
||||
|
||||
u32* data = GPU3D::Renderer == 0
|
||||
? SoftRenderer::AllocateTexture(&texture.Handle, width, height)
|
||||
: NULL;
|
||||
: GLRenderer::AllocateTexture(&texture.Handle, width, height);
|
||||
|
||||
memset(texture.TextureMask, 0, 8*8);
|
||||
memset(texture.PaletteMask, 0, 8*2);
|
||||
@ -525,7 +527,7 @@ ExternalTexHandle GetTexture(u32 texParam, u32 palBase)
|
||||
}
|
||||
|
||||
if (GPU3D::Renderer == 1)
|
||||
{}
|
||||
GLRenderer::FinaliseTexture(texture.Handle, width, height);
|
||||
}
|
||||
|
||||
return texture.Handle;
|
||||
|
@ -63,6 +63,9 @@
|
||||
#define DO_PROCLIST(func) \
|
||||
DO_PROCLIST_1_3(func) \
|
||||
\
|
||||
func(GLTEXIMAGE3D, glTexImage3D); \
|
||||
func(GLTEXSUBIMAGE3D, glTexSubImage3D); \
|
||||
\
|
||||
func(GLGENFRAMEBUFFERS, glGenFramebuffers); \
|
||||
func(GLDELETEFRAMEBUFFERS, glDeleteFramebuffers); \
|
||||
func(GLBINDFRAMEBUFFER, glBindFramebuffer); \
|
||||
|
1356
src/xbrz/xbrz.cpp
Normal file
1356
src/xbrz/xbrz.cpp
Normal file
File diff suppressed because it is too large
Load Diff
79
src/xbrz/xbrz.h
Normal file
79
src/xbrz/xbrz.h
Normal file
@ -0,0 +1,79 @@
|
||||
// ****************************************************************************
|
||||
// * This file is part of the xBRZ project. It is distributed under *
|
||||
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
|
||||
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
|
||||
// * *
|
||||
// * Additionally and as a special exception, the author gives permission *
|
||||
// * to link the code of this program with the following libraries *
|
||||
// * (or with modified versions that use the same licenses), and distribute *
|
||||
// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe *
|
||||
// * You must obey the GNU General Public License in all respects for all of *
|
||||
// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. *
|
||||
// * If you modify this file, you may extend this exception to your version *
|
||||
// * of the file, but you are not obligated to do so. If you do not wish to *
|
||||
// * do so, delete this exception statement from your version. *
|
||||
// ****************************************************************************
|
||||
|
||||
#ifndef XBRZ_HEADER_3847894708239054
|
||||
#define XBRZ_HEADER_3847894708239054
|
||||
|
||||
#include <cstddef> //size_t
|
||||
#include <cstdint> //uint32_t
|
||||
#include <limits>
|
||||
#include "xbrz_config.h"
|
||||
|
||||
|
||||
namespace xbrz
|
||||
{
|
||||
/*
|
||||
-------------------------------------------------------------------------
|
||||
| xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju |
|
||||
-------------------------------------------------------------------------
|
||||
using a modified approach of xBR:
|
||||
http://board.byuu.org/viewtopic.php?f=10&t=2248
|
||||
- new rule set preserving small image features
|
||||
- highly optimized for performance
|
||||
- support alpha channel
|
||||
- support multithreading
|
||||
- support 64-bit architectures
|
||||
- support processing image slices
|
||||
- support scaling up to 6xBRZ
|
||||
*/
|
||||
|
||||
enum class ColorFormat //from high bits -> low bits, 8 bit per channel
|
||||
{
|
||||
RGB, //8 bit for each red, green, blue, upper 8 bits unused
|
||||
ARGB, //including alpha channel, BGRA byte order on little-endian machines
|
||||
ARGB_UNBUFFERED, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time
|
||||
};
|
||||
|
||||
const int SCALE_FACTOR_MAX = 6;
|
||||
|
||||
/*
|
||||
-> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only
|
||||
-> if your emulator changes only a few image slices during each cycle (e.g. DOSBox) then there's no need to run xBRZ on the complete image:
|
||||
Just make sure you enlarge the source image slice by 2 rows on top and 2 on bottom (this is the additional range the xBRZ algorithm is using during analysis)
|
||||
CAVEAT: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition
|
||||
in the target image data if you are using multiple threads for processing each enlarged slice!
|
||||
|
||||
THREAD-SAFETY: - parts of the same image may be scaled by multiple threads as long as the [yFirst, yLast) ranges do not overlap!
|
||||
- there is a minor inefficiency for the first row of a slice, so avoid processing single rows only; suggestion: process at least 8-16 rows
|
||||
*/
|
||||
void scale(size_t factor, //valid range: 2 - SCALE_FACTOR_MAX
|
||||
const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight,
|
||||
ColorFormat colFmt,
|
||||
const ScalerCfg& cfg = ScalerCfg(),
|
||||
int yFirst = 0, int yLast = std::numeric_limits<int>::max()); //slice of source image
|
||||
|
||||
void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight,
|
||||
/**/ uint32_t* trg, int trgWidth, int trgHeight);
|
||||
|
||||
void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight,
|
||||
/**/ uint32_t* trg, int trgWidth, int trgHeight);
|
||||
|
||||
|
||||
//parameter tuning
|
||||
bool equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance);
|
||||
}
|
||||
|
||||
#endif
|
35
src/xbrz/xbrz_config.h
Normal file
35
src/xbrz/xbrz_config.h
Normal file
@ -0,0 +1,35 @@
|
||||
// ****************************************************************************
|
||||
// * This file is part of the xBRZ project. It is distributed under *
|
||||
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
|
||||
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
|
||||
// * *
|
||||
// * Additionally and as a special exception, the author gives permission *
|
||||
// * to link the code of this program with the following libraries *
|
||||
// * (or with modified versions that use the same licenses), and distribute *
|
||||
// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe *
|
||||
// * You must obey the GNU General Public License in all respects for all of *
|
||||
// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. *
|
||||
// * If you modify this file, you may extend this exception to your version *
|
||||
// * of the file, but you are not obligated to do so. If you do not wish to *
|
||||
// * do so, delete this exception statement from your version. *
|
||||
// ****************************************************************************
|
||||
|
||||
#ifndef XBRZ_CONFIG_HEADER_284578425345
|
||||
#define XBRZ_CONFIG_HEADER_284578425345
|
||||
|
||||
//do NOT include any headers here! used by xBRZ_dll!!!
|
||||
|
||||
namespace xbrz
|
||||
{
|
||||
struct ScalerCfg
|
||||
{
|
||||
double luminanceWeight = 1;
|
||||
double equalColorTolerance = 30;
|
||||
double centerDirectionBias = 4;
|
||||
double dominantDirectionThreshold = 3.6;
|
||||
double steepDirectionThreshold = 2.2;
|
||||
double newTestAttribute = 0; //unused; test new parameters
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
266
src/xbrz/xbrz_tools.h
Normal file
266
src/xbrz/xbrz_tools.h
Normal file
@ -0,0 +1,266 @@
|
||||
// ****************************************************************************
|
||||
// * This file is part of the xBRZ project. It is distributed under *
|
||||
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
|
||||
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
|
||||
// * *
|
||||
// * Additionally and as a special exception, the author gives permission *
|
||||
// * to link the code of this program with the following libraries *
|
||||
// * (or with modified versions that use the same licenses), and distribute *
|
||||
// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe *
|
||||
// * You must obey the GNU General Public License in all respects for all of *
|
||||
// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. *
|
||||
// * If you modify this file, you may extend this exception to your version *
|
||||
// * of the file, but you are not obligated to do so. If you do not wish to *
|
||||
// * do so, delete this exception statement from your version. *
|
||||
// ****************************************************************************
|
||||
|
||||
#ifndef XBRZ_TOOLS_H_825480175091875
|
||||
#define XBRZ_TOOLS_H_825480175091875
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
|
||||
|
||||
namespace xbrz
|
||||
{
|
||||
template <uint32_t N> inline
|
||||
unsigned char getByte(uint32_t val) { return static_cast<unsigned char>((val >> (8 * N)) & 0xff); }
|
||||
|
||||
inline unsigned char getAlpha(uint32_t pix) { return getByte<3>(pix); }
|
||||
inline unsigned char getRed (uint32_t pix) { return getByte<2>(pix); }
|
||||
inline unsigned char getGreen(uint32_t pix) { return getByte<1>(pix); }
|
||||
inline unsigned char getBlue (uint32_t pix) { return getByte<0>(pix); }
|
||||
|
||||
inline uint32_t makePixel(unsigned char a, unsigned char r, unsigned char g, unsigned char b) { return (a << 24) | (r << 16) | (g << 8) | b; }
|
||||
inline uint32_t makePixel( unsigned char r, unsigned char g, unsigned char b) { return (r << 16) | (g << 8) | b; }
|
||||
|
||||
inline uint32_t rgb555to888(uint16_t pix) { return ((pix & 0x7C00) << 9) | ((pix & 0x03E0) << 6) | ((pix & 0x001F) << 3); }
|
||||
inline uint32_t rgb565to888(uint16_t pix) { return ((pix & 0xF800) << 8) | ((pix & 0x07E0) << 5) | ((pix & 0x001F) << 3); }
|
||||
|
||||
inline uint16_t rgb888to555(uint32_t pix) { return static_cast<uint16_t>(((pix & 0xF80000) >> 9) | ((pix & 0x00F800) >> 6) | ((pix & 0x0000F8) >> 3)); }
|
||||
inline uint16_t rgb888to565(uint32_t pix) { return static_cast<uint16_t>(((pix & 0xF80000) >> 8) | ((pix & 0x00FC00) >> 5) | ((pix & 0x0000F8) >> 3)); }
|
||||
|
||||
|
||||
template <class Pix> inline
|
||||
Pix* byteAdvance(Pix* ptr, int bytes)
|
||||
{
|
||||
using PixNonConst = typename std::remove_cv<Pix>::type;
|
||||
using PixByte = typename std::conditional<std::is_same<Pix, PixNonConst>::value, char, const char>::type;
|
||||
|
||||
static_assert(std::is_integral<PixNonConst>::value, "Pix* is expected to be cast-able to char*");
|
||||
|
||||
return reinterpret_cast<Pix*>(reinterpret_cast<PixByte*>(ptr) + bytes);
|
||||
}
|
||||
|
||||
|
||||
//fill block with the given color
|
||||
template <class Pix> inline
|
||||
void fillBlock(Pix* trg, int pitch /*[bytes]*/, Pix col, int blockWidth, int blockHeight)
|
||||
{
|
||||
//for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch))
|
||||
// std::fill(trg, trg + blockWidth, col);
|
||||
|
||||
for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch))
|
||||
for (int x = 0; x < blockWidth; ++x)
|
||||
trg[x] = col;
|
||||
}
|
||||
|
||||
|
||||
//nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!)
|
||||
template <class PixSrc, class PixTrg, class PixConverter>
|
||||
void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/,
|
||||
/**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/,
|
||||
int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/)
|
||||
{
|
||||
static_assert(std::is_integral<PixSrc>::value, "PixSrc* is expected to be cast-able to char*");
|
||||
static_assert(std::is_integral<PixTrg>::value, "PixTrg* is expected to be cast-able to char*");
|
||||
|
||||
static_assert(std::is_same<decltype(pixCvrt(PixSrc())), PixTrg>::value, "PixConverter returning wrong pixel format");
|
||||
|
||||
if (srcPitch < srcWidth * static_cast<int>(sizeof(PixSrc)) ||
|
||||
trgPitch < trgWidth * static_cast<int>(sizeof(PixTrg)))
|
||||
{
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
yFirst = std::max(yFirst, 0);
|
||||
yLast = std::min(yLast, trgHeight);
|
||||
if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return;
|
||||
|
||||
for (int y = yFirst; y < yLast; ++y)
|
||||
{
|
||||
const int ySrc = srcHeight * y / trgHeight;
|
||||
const PixSrc* const srcLine = byteAdvance(src, ySrc * srcPitch);
|
||||
PixTrg* const trgLine = byteAdvance(trg, y * trgPitch);
|
||||
|
||||
for (int x = 0; x < trgWidth; ++x)
|
||||
{
|
||||
const int xSrc = srcWidth * x / trgWidth;
|
||||
trgLine[x] = pixCvrt(srcLine[xSrc]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//nearest-neighbor (going over source image - fast for upscaling, since source is read only once
|
||||
template <class PixSrc, class PixTrg, class PixConverter>
|
||||
void nearestNeighborScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/,
|
||||
/**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/,
|
||||
int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/)
|
||||
{
|
||||
static_assert(std::is_integral<PixSrc>::value, "PixSrc* is expected to be cast-able to char*");
|
||||
static_assert(std::is_integral<PixTrg>::value, "PixTrg* is expected to be cast-able to char*");
|
||||
|
||||
static_assert(std::is_same<decltype(pixCvrt(PixSrc())), PixTrg>::value, "PixConverter returning wrong pixel format");
|
||||
|
||||
if (srcPitch < srcWidth * static_cast<int>(sizeof(PixSrc)) ||
|
||||
trgPitch < trgWidth * static_cast<int>(sizeof(PixTrg)))
|
||||
{
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
yFirst = std::max(yFirst, 0);
|
||||
yLast = std::min(yLast, srcHeight);
|
||||
if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return;
|
||||
|
||||
for (int y = yFirst; y < yLast; ++y)
|
||||
{
|
||||
//mathematically: ySrc = floor(srcHeight * yTrg / trgHeight)
|
||||
// => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight
|
||||
|
||||
//keep within for loop to support MT input slices!
|
||||
const int yTrgFirst = ( y * trgHeight + srcHeight - 1) / srcHeight; //=ceil(y * trgHeight / srcHeight)
|
||||
const int yTrgLast = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight; //=ceil(((y + 1) * trgHeight) / srcHeight)
|
||||
const int blockHeight = yTrgLast - yTrgFirst;
|
||||
|
||||
if (blockHeight > 0)
|
||||
{
|
||||
const PixSrc* srcLine = byteAdvance(src, y * srcPitch);
|
||||
/**/ PixTrg* trgLine = byteAdvance(trg, yTrgFirst * trgPitch);
|
||||
int xTrgFirst = 0;
|
||||
|
||||
for (int x = 0; x < srcWidth; ++x)
|
||||
{
|
||||
const int xTrgLast = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth;
|
||||
const int blockWidth = xTrgLast - xTrgFirst;
|
||||
if (blockWidth > 0)
|
||||
{
|
||||
xTrgFirst = xTrgLast;
|
||||
|
||||
const auto trgPix = pixCvrt(srcLine[x]);
|
||||
fillBlock(trgLine, trgPitch, trgPix, blockWidth, blockHeight);
|
||||
trgLine += blockWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <class PixTrg, class PixConverter>
|
||||
void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch,
|
||||
/**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch,
|
||||
int yFirst, int yLast, PixConverter pixCvrt /*convert uint32_t to PixTrg*/)
|
||||
{
|
||||
static_assert(std::is_integral<PixTrg>::value, "PixTrg* is expected to be cast-able to char*");
|
||||
static_assert(std::is_same<decltype(pixCvrt(uint32_t())), PixTrg>::value, "PixConverter returning wrong pixel format");
|
||||
|
||||
if (srcPitch < srcWidth * static_cast<int>(sizeof(uint32_t)) ||
|
||||
trgPitch < trgWidth * static_cast<int>(sizeof(PixTrg)))
|
||||
{
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
yFirst = std::max(yFirst, 0);
|
||||
yLast = std::min(yLast, trgHeight);
|
||||
if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return;
|
||||
|
||||
const double scaleX = static_cast<double>(trgWidth ) / srcWidth;
|
||||
const double scaleY = static_cast<double>(trgHeight) / srcHeight;
|
||||
|
||||
//perf notes:
|
||||
// -> double-based calculation is (slightly) faster than float
|
||||
// -> pre-calculation gives significant boost; std::vector<> memory allocation is negligible!
|
||||
struct CoeffsX
|
||||
{
|
||||
int x1 = 0;
|
||||
int x2 = 0;
|
||||
double xx1 = 0;
|
||||
double x2x = 0;
|
||||
};
|
||||
std::vector<CoeffsX> buf(trgWidth);
|
||||
for (int x = 0; x < trgWidth; ++x)
|
||||
{
|
||||
const int x1 = srcWidth * x / trgWidth;
|
||||
int x2 = x1 + 1;
|
||||
if (x2 == srcWidth) --x2;
|
||||
|
||||
const double xx1 = x / scaleX - x1;
|
||||
const double x2x = 1 - xx1;
|
||||
|
||||
buf[x] = { x1, x2, xx1, x2x };
|
||||
}
|
||||
|
||||
for (int y = yFirst; y < yLast; ++y)
|
||||
{
|
||||
const int y1 = srcHeight * y / trgHeight;
|
||||
int y2 = y1 + 1;
|
||||
if (y2 == srcHeight) --y2;
|
||||
|
||||
const double yy1 = y / scaleY - y1;
|
||||
const double y2y = 1 - yy1;
|
||||
|
||||
const uint32_t* const srcLine = byteAdvance(src, y1 * srcPitch);
|
||||
const uint32_t* const srcLineNext = byteAdvance(src, y2 * srcPitch);
|
||||
PixTrg* const trgLine = byteAdvance(trg, y * trgPitch);
|
||||
|
||||
for (int x = 0; x < trgWidth; ++x)
|
||||
{
|
||||
//perf: do NOT "simplify" the variable layout without measurement!
|
||||
const int x1 = buf[x].x1;
|
||||
const int x2 = buf[x].x2;
|
||||
const double xx1 = buf[x].xx1;
|
||||
const double x2x = buf[x].x2x;
|
||||
|
||||
const double x2xy2y = x2x * y2y;
|
||||
const double xx1y2y = xx1 * y2y;
|
||||
const double x2xyy1 = x2x * yy1;
|
||||
const double xx1yy1 = xx1 * yy1;
|
||||
|
||||
auto interpolate = [=](int offset)
|
||||
{
|
||||
/* https://en.wikipedia.org/wiki/Bilinear_interpolation
|
||||
(c11(x2 - x) + c21(x - x1)) * (y2 - y ) +
|
||||
(c12(x2 - x) + c22(x - x1)) * (y - y1) */
|
||||
const auto c11 = (srcLine [x1] >> (8 * offset)) & 0xff;
|
||||
const auto c21 = (srcLine [x2] >> (8 * offset)) & 0xff;
|
||||
const auto c12 = (srcLineNext[x1] >> (8 * offset)) & 0xff;
|
||||
const auto c22 = (srcLineNext[x2] >> (8 * offset)) & 0xff;
|
||||
|
||||
return c11 * x2xy2y + c21 * xx1y2y +
|
||||
c12 * x2xyy1 + c22 * xx1yy1;
|
||||
};
|
||||
|
||||
const double bi = interpolate(0);
|
||||
const double gi = interpolate(1);
|
||||
const double ri = interpolate(2);
|
||||
const double ai = interpolate(3);
|
||||
|
||||
const auto b = static_cast<uint32_t>(bi + 0.5);
|
||||
const auto g = static_cast<uint32_t>(gi + 0.5);
|
||||
const auto r = static_cast<uint32_t>(ri + 0.5);
|
||||
const auto a = static_cast<uint32_t>(ai + 0.5);
|
||||
|
||||
const uint32_t trgPix = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
|
||||
trgLine[x] = pixCvrt(trgPix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //XBRZ_TOOLS_H_825480175091875
|
Loading…
Reference in New Issue
Block a user