fixes and tex cache support for OGL renderer

includes xbrz upscaling, currently hardcoded to x2
This commit is contained in:
RSDuck 2020-03-24 00:59:49 +01:00
parent ed61867dec
commit c70519070b
14 changed files with 1940 additions and 416 deletions

View File

@ -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)

View File

@ -28,6 +28,7 @@ add_library(core STATIC
xxhash/xxhash.c
stb/stb.cpp
xbrz/xbrz.cpp
)
if (WIN32)

View File

@ -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

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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];
if (cur_rp->PrimType != primtype) break;
if (cur_rp->RenderKey != key) break;
textureChange = false;
numpolys++;
numindices += cur_rp->NumIndices;
}
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);
glDrawElements(primtype, numindices, GL_UNSIGNED_SHORT, rp->Indices);
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

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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

File diff suppressed because it is too large Load Diff

79
src/xbrz/xbrz.h Normal file
View 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
View 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
View 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