Dynamic tilepacking for indexed colour textures

git-svn-id: https://svn.eduke32.com/eduke32@6883 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
pogokeen 2018-05-08 17:32:16 +00:00
parent 3ad44df555
commit dcdffb73d0
8 changed files with 655 additions and 79 deletions

View File

@ -228,7 +228,7 @@ else
endif
endif
ifeq (1,$(USE_OPENGL))
engine_objs += voxmodel.cpp mdsprite.cpp
engine_objs += voxmodel.cpp mdsprite.cpp tilepacker.cpp
engine_deps += glad
ifeq (1,$(POLYMER))
engine_objs += glbuild.cpp polymer.cpp

View File

@ -817,6 +817,14 @@ typedef struct {
EDUKE32_STATIC_ASSERT(sizeof(vec3d_t) == sizeof(double) * 3);
typedef struct {
float x, y, z, w;
} vec4f_t;
typedef struct {
float x, y, z, w;
} vec4d_t;
////////// Language tricks that depend on size_t //////////

View File

@ -32,6 +32,7 @@ extern void Polymost_prepare_loadboard(void);
//void phex(char v, char *s);
void uploadtexture(int32_t doalloc, vec2_t siz, int32_t texfmt, coltype *pic, vec2_t tsiz, int32_t dameth);
void uploadtextureindexed(int32_t doalloc, vec2_t siz, vec2_t offset, intptr_t tile);
void uploadbasepalette(int32_t basepalnum);
void uploadpalswap(int32_t palookupnum);
void polymost_drawsprite(int32_t snum);
@ -45,13 +46,22 @@ void polymost_drawrooms(void);
void polymost_resetVertexPointers(void);
void polymost_disableProgram(void);
void polymost_resetProgram(void);
void polymost_setTexturePosSize(vec4f_t texturePosSize);
void polymost_setFogEnabled(char fogEnabled);
void polymost_useColorOnly(char useColorOnly);
void polymost_usePaletteIndexing(char usePaletteIndexing);
void polymost_useDetailMapping(char useDetailMapping);
void polymost_useGlowMapping(char useGlowMapping);
void polymost_activeTexture(GLenum texture);
void polymost_bindTexture(GLenum target, uint32_t textureID);
void useShaderProgram(uint32_t shaderID);
//POGOTODO: these wrappers won't be needed down the line -- remove them once proper draw call organization is finished
#undef glActiveTexture
#undef glBindTexture
#define glActiveTexture polymost_activeTexture
#define glBindTexture polymost_bindTexture
void polymost_glinit(void);
void polymost_glreset(void);

View File

@ -0,0 +1,54 @@
/*
* tilepacker.h
* A k-d tree based bin packer that organizes rectangular tiles to fit
* neatly into one texture.
*
* Copyright © 2018, Alex Dawson. All rights reserved.
*/
#ifndef TILEPACKER_H_
#define TILEPACKER_H_
#define MAXTILESHEETS 64
#define MAXPACKEDTILES MAXTILES+1
typedef struct
{
uint32_t u, v, width, height;
} TileRect;
typedef struct
{
uint32_t tilesheetID;
TileRect rect;
} Tile;
// Initialize the specified tilesheet
// tilesheetId must be less than MAXTILESHEETS
// Re-initializing an existing tilesheet will discard all of its contents
void tilepacker_initTilesheet(uint32_t tilesheetID, uint32_t tilesheetWidth, uint32_t tilesheetHeight);
// Adds a tile into sorted collection to be packed
// uid can be any unique key -- picnums are used for indexed colour textures
void tilepacker_addTile(uint32_t tileUID, uint32_t tileWidth, uint32_t tileHeight);
// Packs the sorted collection of tiles that have been added with tilepacker_addTile()
// Returns true if all nodes could be packed succesfully into the specified tilesheet
// tilepacker_pack can be called again with a different tilesheetId to pack the remaining tiles
char tilepacker_pack(uint32_t tilesheetID);
// Discard the rejected tiles so that they will not be carried over to the next call to tilepacker_pack
void tilepacker_discardRejects();
// Sets pOutput to contain the Tile information for a packed tile with the given tileUID
// Returns true if the Tile has been packed, false otherwise
// If pOutput is NULL, the function solely returns whether or not the Tile has been packed
char tilepacker_getTile(uint32_t tileUID, Tile *pOutput);
// Returns true if the Tile with tileUID has been packed, false otherwise
static inline char tilepacker_isTilePacked(uint32_t tileUID)
{
return tilepacker_getTile(tileUID, NULL);
};
#endif /* TILEPACKER_H_ */

View File

@ -2293,6 +2293,9 @@ static int32_t polymost_md3draw(md3model_t *m, const uspritetype *tspr)
float const xpanning = (float)sext->xpanning * (1.f/256.f);
float const ypanning = (float)sext->ypanning * (1.f/256.f);
polymost_usePaletteIndexing(false);
polymost_setTexturePosSize({ 0.f, 0.f, 1.f, 1.f });
for (surfi=0; surfi<m->head.numsurfs; surfi++)
{
//PLAG : sorting stuff
@ -2593,6 +2596,7 @@ static int32_t polymost_md3draw(md3model_t *m, const uspritetype *tspr)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
polymost_usePaletteIndexing(true);
polymost_resetVertexPointers();
globalnoeffect=0;
@ -2756,19 +2760,9 @@ int32_t polymost_mddraw(const uspritetype *tspr)
mdmodel_t *const vm = models[tile2model[Ptile2tile(tspr->picnum,
(tspr->owner >= MAXSPRITES) ? tspr->pal : sprite[tspr->owner].pal)].modelid];
if (vm->mdnum == 1)
{
polymost_usePaletteIndexing(false);
int ret = polymost_voxdraw((voxmodel_t *)vm,tspr);
polymost_usePaletteIndexing(true);
return ret;
}
if (vm->mdnum == 3)
{
polymost_usePaletteIndexing(false);
int ret = polymost_md3draw((md3model_t *)vm,tspr);
polymost_usePaletteIndexing(true);
return ret;
}
return polymost_voxdraw((voxmodel_t *)vm,tspr);
else if (vm->mdnum == 3)
return polymost_md3draw((md3model_t *)vm,tspr);
return 0;
}

View File

@ -24,6 +24,7 @@ Ken Silverman's official web site: http://www.advsys.net/ken
#include "texcache.h"
#include "common.h"
#include "palette.h"
#include "tilepacker.h"
#ifndef _WIN32
extern int32_t filelength(int h); // kplib.c
@ -79,7 +80,7 @@ int32_t drawpolyVertsBufferLength = r_drawpolyVertsBufferLength;
static GLuint drawpolyVertsID = 0;
static GLint drawpolyVertsOffset = 0;
static int32_t drawpolyVertsSubBufferIndex = 0;
static GLsync drawpolyVertsSync[3] = {0};
static GLsync drawpolyVertsSync[3] = { 0 };
static float defaultDrawpolyVertsArray[MAX_DRAWPOLY_VERTS*5];
static float* drawpolyVerts = defaultDrawpolyVertsArray;
@ -136,8 +137,9 @@ static float fogresult, fogresult2;
coltypef fogcol, fogtable[MAXPALOOKUPS];
static uint32_t currentShaderProgramID = 0;
static GLenum currentActiveTexture = 0;
static uint32_t currentTextureID = 0;
static GLuint blankTextureID = 0;
static GLuint quadVertsID = 0;
static GLuint polymost2BasicShaderProgramID = 0;
static GLint texSamplerLoc = -1;
@ -153,6 +155,9 @@ static GLint fogColorLoc = -1;
#define PALSWAP_TEXTURE_SIZE 2048
int32_t r_useindexedcolortextures = -1;
static GLuint tilesheetTexIDs[MAXTILESHEETS];
static GLint tilesheetSize = 0;
static vec2f_t tilesheetHalfTexelSize = { 0.f, 0.f };
static int32_t lastbasepal = -1;
static GLuint paletteTextureIDs[MAXBASEPALS];
static GLuint palswapTextureID = 0;
@ -164,11 +169,15 @@ static GLint polymost1PalSwapSamplerLoc = -1;
static GLint polymost1PaletteSamplerLoc = -1;
static GLint polymost1DetailSamplerLoc = -1;
static GLint polymost1GlowSamplerLoc = -1;
static GLint polymost1TexturePosSizeLoc = -1;
static vec4f_t polymost1TexturePosSize = { 0.f, 0.f, 1.f, 1.f };
static GLint polymost1HalfTexelSizeLoc = -1;
static vec2f_t polymost1HalfTexelSize = { 0.f, 0.f };
static GLint polymost1PalswapPosLoc = -1;
static vec2f_t polymost1PalswapPos = {0.f, 0.f};
static vec2f_t polymost1PalswapPos = { 0.f, 0.f };
static GLint polymost1PalswapSizeLoc = -1;
static vec2f_t polymost1PalswapSize = {0.f, 0.f};
static vec2f_t polymost1PalswapInnerSize = {0.f, 0.f};
static vec2f_t polymost1PalswapSize = { 0.f, 0.f };
static vec2f_t polymost1PalswapInnerSize = { 0.f, 0.f };
static GLint polymost1ShadeLoc = -1;
static float polymost1Shade = 0.f;
static GLint polymost1FogEnabledLoc = -1;
@ -580,6 +589,8 @@ static void polymost_setCurrentShaderProgram(uint32_t programID)
polymost1PaletteSamplerLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "s_palette");
polymost1DetailSamplerLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "s_detail");
polymost1GlowSamplerLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "s_glow");
polymost1TexturePosSizeLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "u_texturePosSize");
polymost1HalfTexelSizeLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "u_halfTexelSize");
polymost1PalswapPosLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "u_palswapPos");
polymost1PalswapSizeLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "u_palswapSize");
polymost1ShadeLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "u_shade");
@ -590,6 +601,8 @@ static void polymost_setCurrentShaderProgram(uint32_t programID)
polymost1UseGlowMappingLoc = glGetUniformLocation(polymost1CurrentShaderProgramID, "u_useGlowMapping");
//set the uniforms to the current values
glUniform4f(polymost1TexturePosSizeLoc, polymost1TexturePosSize.x, polymost1TexturePosSize.y, polymost1TexturePosSize.z, polymost1TexturePosSize.w);
glUniform2f(polymost1HalfTexelSizeLoc, polymost1HalfTexelSize.x, polymost1HalfTexelSize.y);
glUniform2f(polymost1PalswapPosLoc, polymost1PalswapPos.x, polymost1PalswapPos.y);
glUniform2f(polymost1PalswapSizeLoc, polymost1PalswapInnerSize.x, polymost1PalswapInnerSize.y);
glUniform1f(polymost1ShadeLoc, polymost1Shade);
@ -600,6 +613,24 @@ static void polymost_setCurrentShaderProgram(uint32_t programID)
glUniform1f(polymost1UseGlowMappingLoc, polymost1UseGlowMapping);
}
void polymost_setTexturePosSize(vec4f_t texturePosSize)
{
if (currentShaderProgramID == polymost1CurrentShaderProgramID)
{
polymost1TexturePosSize = texturePosSize;
glUniform4f(polymost1TexturePosSizeLoc, polymost1TexturePosSize.x, polymost1TexturePosSize.y, polymost1TexturePosSize.z, polymost1TexturePosSize.w);
}
}
static void polymost_setHalfTexelSize(vec2f_t halfTexelSize)
{
if (currentShaderProgramID == polymost1CurrentShaderProgramID)
{
polymost1HalfTexelSize = halfTexelSize;
glUniform2f(polymost1HalfTexelSizeLoc, polymost1HalfTexelSize.x, polymost1HalfTexelSize.y);
}
}
static void polymost_setPalswap(uint32_t index)
{
if (currentShaderProgramID == polymost1CurrentShaderProgramID)
@ -714,6 +745,55 @@ void polymost_useGlowMapping(char useGlowMapping)
}
}
void polymost_activeTexture(GLenum texture)
{
currentActiveTexture = texture;
glad_glActiveTexture(texture);
}
//POGOTODO: replace this and polymost_activeTexture with proper draw call organization
void polymost_bindTexture(GLenum target, uint32_t textureID)
{
if (currentTextureID != textureID ||
textureID == 0 ||
currentActiveTexture != GL_TEXTURE0 ||
videoGetRenderMode() != REND_POLYMOST)
{
glad_glBindTexture(target, textureID);
if (currentActiveTexture == GL_TEXTURE0)
{
currentTextureID = textureID;
}
}
}
static void polymost_bindPth(pthtyp *pPth)
{
vec4f_t texturePosSize = { 0.f, 0.f, 1.f, 1.f };
vec2f_t halfTexelSize = { 0.f, 0.f };
if (r_useindexedcolortextures &&
!(pPth->flags & PTH_HIGHTILE))
{
Tile tile;
char tileIsPacked = tilepacker_getTile(waloff[pPth->picnum] ? pPth->picnum+1 : 0, &tile);
//POGO: check the width and height to ensure that the tile hasn't been changed for a user tile that has different dimensions
if (tileIsPacked &&
(!waloff[pPth->picnum] ||
(tile.rect.width == (uint32_t) tilesiz[pPth->picnum].y &&
tile.rect.height == (uint32_t) tilesiz[pPth->picnum].x)))
{
texturePosSize = { tile.rect.u/(float) tilesheetSize,
tile.rect.v/(float) tilesheetSize,
tile.rect.width/(float) tilesheetSize,
tile.rect.height/(float) tilesheetSize };
halfTexelSize = tilesheetHalfTexelSize;
}
}
polymost_setTexturePosSize(texturePosSize);
polymost_setHalfTexelSize(halfTexelSize);
glBindTexture(GL_TEXTURE_2D, pPth->glpic);
}
void useShaderProgram(uint32_t shaderID)
{
glUseProgram(shaderID);
@ -754,6 +834,8 @@ void polymost_glinit()
}
#endif
//POGOTODO: require a max texture size >= 2048
persistentStreamBuffer = r_persistentStreamBuffer;
drawpolyVertsBufferLength = r_drawpolyVertsBufferLength;
@ -782,13 +864,43 @@ void polymost_glinit()
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
const char blankTex[] = {255, 0, 0, 0, 255, 0, 0, 0,
255, 0, 0, 0, 255, 0, 0, 0};
glGenTextures(1, &blankTextureID);
glBindTexture(GL_TEXTURE_2D, blankTextureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, blankTex);
currentTextureID = 0;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &tilesheetSize);
tilesheetHalfTexelSize = { 0.5f/tilesheetSize, 0.5f/tilesheetSize };
vec2_t maxTexDimensions = { tilesheetSize, tilesheetSize };
char allPacked = false;
static uint32_t numTilesheets = 0;
//POGO: only pack the tilesheets once
if (numTilesheets == 0)
{
// add a blank texture for tileUID 0
tilepacker_addTile(0, 2, 2);
for (int picnum = 0; picnum < MAXTILES; ++picnum)
{
tilepacker_addTile(picnum+1, (uint32_t) tilesiz[picnum].y, (uint32_t) tilesiz[picnum].x);
}
do
{
tilepacker_initTilesheet(numTilesheets, tilesheetSize, tilesheetSize);
allPacked = tilepacker_pack(numTilesheets);
++numTilesheets;
} while (!allPacked && numTilesheets < MAXTILESHEETS);
}
for (uint32_t i = 0; i < numTilesheets; ++i)
{
glGenTextures(1, tilesheetTexIDs+i);
glBindTexture(GL_TEXTURE_2D, tilesheetTexIDs[i]);
uploadtextureindexed(true, {0, 0}, maxTexDimensions, (intptr_t) NULL);
}
const char blankTex[] = {255, 255,
255, 255};
Tile blankTile;
tilepacker_getTile(0, &blankTile);
glBindTexture(GL_TEXTURE_2D, tilesheetTexIDs[blankTile.tilesheetID]);
uploadtextureindexed(false, {(int32_t) blankTile.rect.u, (int32_t) blankTile.rect.v}, {2, 2}, (intptr_t) blankTex);
quadVertsID = ids[1];
glBindBuffer(GL_ARRAY_BUFFER, quadVertsID);
@ -890,9 +1002,12 @@ void polymost_glinit()
const char* const POLYMOST1_BASIC_VERTEX_SHADER_CODE =
"#version 110\n\
\n\
// output\n\
varying vec4 v_color;\n\
\n\
//u_texturePosSize is the texture position & size packaged into a single vec4 as {pos.x, pos.y, size.x, size.y}\n\
uniform vec4 u_texturePosSize;\n\
uniform float u_usePalette;\n\
\n\
const float c_zero = 0.0;\n\
const float c_one = 1.0;\n\
\n\
@ -904,6 +1019,11 @@ void polymost_glinit()
eyeCoordPosition.xyz /= eyeCoordPosition.w;\n\
\n\
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;\n\
gl_TexCoord[0] = mix(gl_TexCoord[0].xyzw, gl_TexCoord[0].yxzw, u_usePalette);\n\
\n\
gl_TexCoord[3] = gl_TextureMatrix[3] * gl_MultiTexCoord3;\n\
gl_TexCoord[4] = gl_TextureMatrix[4] * gl_MultiTexCoord4;\n\
\n\
gl_FogFragCoord = abs(eyeCoordPosition.z);\n\
//gl_FogFragCoord = clamp((gl_Fog.end-abs(eyeCoordPosition.z))*gl_Fog.scale, c_zero, c_one);\n\
\n\
@ -919,8 +1039,12 @@ void polymost_glinit()
//s_palette is the base palette texture where u is the color index\n\
uniform sampler2D s_palette;\n\
\n\
//u_texturePosSize is the texture position & size packaged into a single vec4 as {pos.x, pos.y, size.x, size.y}\n\
uniform vec4 u_texturePosSize;\n\
uniform vec2 u_halfTexelSize;\n\
uniform vec2 u_palswapPos;\n\
uniform vec2 u_palswapSize;\n\
\n\
uniform float u_shade;\n\
uniform float u_fogEnabled;\n\
\n\
@ -933,15 +1057,20 @@ void polymost_glinit()
const float c_basepalOffset = 0.5/256.0;\n\
\n\
const float c_zero = 0.0;\n\
const float c_one = 1.0;\n\
const float c_one = 1.0;\n\
const float c_two = 2.0;\n\
const vec4 c_vec4_one = vec4(c_one);\n\
const float c_wrapThreshold = 0.90;\n\
\n\
void main()\n\
{\n\
vec2 texCoord = (gl_TextureMatrix[0] * gl_TexCoord[0]).xy;\n\
texCoord = mix(texCoord.xy, texCoord.yx, u_usePalette);\n\
//GLSL 130+ could alternatively use texture2DGrad()\n\
vec2 transitionBlend = fwidth(floor(gl_TexCoord[0].xy));\n\
transitionBlend = fwidth(transitionBlend)+transitionBlend;\n\
vec2 texCoord = mix(fract(gl_TexCoord[0].xy), abs(c_one-mod(gl_TexCoord[0].xy+c_one, c_two)), transitionBlend);\n\
texCoord = clamp(u_texturePosSize.zw*texCoord, u_halfTexelSize, u_texturePosSize.zw-u_halfTexelSize);\n\
vec4 color = texture2D(s_texture, u_texturePosSize.xy+texCoord);\n\
\n\
vec4 color = texture2D(s_texture, texCoord);\n\
float colorIndex = texture2D(s_palswap, u_palswapPos+u_palswapSize*vec2(color.r, u_shade)).r;\n\
colorIndex = c_basepalOffset + c_basepalScale*colorIndex;\n\
vec4 palettedColor = texture2D(s_palette, vec2(colorIndex, c_zero));\n\
@ -980,8 +1109,12 @@ void polymost_glinit()
uniform sampler2D s_detail;\n\
uniform sampler2D s_glow;\n\
\n\
//u_texturePosSize is the texture position & size packaged into a single vec4 as {pos.x, pos.y, size.x, size.y}\n\
uniform vec4 u_texturePosSize;\n\
uniform vec2 u_halfTexelSize;\n\
uniform vec2 u_palswapPos;\n\
uniform vec2 u_palswapSize;\n\
\n\
uniform float u_shade;\n\
uniform float u_fogEnabled;\n\
\n\
@ -997,15 +1130,20 @@ void polymost_glinit()
const float c_basepalOffset = 0.5/256.0;\n\
\n\
const float c_zero = 0.0;\n\
const float c_one = 1.0;\n\
const float c_one = 1.0;\n\
const float c_two = 2.0;\n\
const vec4 c_vec4_one = vec4(c_one);\n\
const float c_wrapThreshold = 0.95;\n\
\n\
void main()\n\
{\n\
vec2 texCoord = (gl_TextureMatrix[0] * gl_TexCoord[0]).xy;\n\
texCoord = mix(texCoord.xy, texCoord.yx, u_usePalette);\n\
//GLSL 130+ could alternatively use texture2DGrad()\n\
vec2 transitionBlend = fwidth(floor(gl_TexCoord[0].xy));\n\
transitionBlend = fwidth(transitionBlend)+transitionBlend;\n\
vec2 texCoord = mix(fract(gl_TexCoord[0].xy), abs(c_one-mod(gl_TexCoord[0].xy+c_one, c_two)), transitionBlend);\n\
texCoord = clamp(u_texturePosSize.zw*texCoord, u_halfTexelSize, u_texturePosSize.zw-u_halfTexelSize);\n\
vec4 color = texture2D(s_texture, u_texturePosSize.xy+texCoord);\n\
\n\
vec4 color = texture2D(s_texture, texCoord);\n\
float colorIndex = texture2D(s_palswap, u_palswapPos+u_palswapSize*vec2(color.r, u_shade)).r;\n\
colorIndex = c_basepalOffset + c_basepalScale*colorIndex;\n\
vec4 palettedColor = texture2D(s_palette, vec2(colorIndex, c_zero));\n\
@ -1013,7 +1151,7 @@ void polymost_glinit()
palettedColor.a = c_one-floor(color.r);\n\
color = mix(color, palettedColor, u_usePalette);\n\
\n\
vec4 detailColor = texture2D(s_detail, (gl_TextureMatrix[3] * gl_TexCoord[0]).xy);\n\
vec4 detailColor = texture2D(s_detail, gl_TexCoord[3].xy);\n\
detailColor = mix(c_vec4_one, 2.0*detailColor, u_useDetailMapping*detailColor.a);\n\
color.rgb *= detailColor.rgb;\n\
\n\
@ -1031,7 +1169,7 @@ void polymost_glinit()
color.rgb *= v_color.rgb;\n\
color.rgb = mix(gl_Fog.color.rgb, color.rgb, fogFactor);\n\
\n\
vec4 glowColor = texture2D(s_glow, (gl_TextureMatrix[4] * gl_TexCoord[0]).xy);\n\
vec4 glowColor = texture2D(s_glow, gl_TexCoord[4].xy);\n\
color.rgb = mix(color.rgb, glowColor.rgb, u_useGlowMapping*glowColor.a*(c_one-u_useColorOnly));\n\
\n\
color.a *= v_color.a;\n\
@ -1060,10 +1198,6 @@ void polymost_glinit()
glUniform1i(polymost1DetailSamplerLoc, 3);
glUniform1i(polymost1GlowSamplerLoc, 4);
polymost_setPalswapSize(256, numshades+1);
glUniform1f(polymost1UseColorOnlyLoc, polymost1UseColorOnly);
glUniform1f(polymost1UsePaletteLoc, polymost1UsePalette);
glUniform1f(polymost1UseDetailMappingLoc, polymost1UseDetailMapping);
glUniform1f(polymost1UseGlowMappingLoc, polymost1UseGlowMapping);
polymost_setCurrentShaderProgram(polymost1BasicShaderProgramID);
glUniform1i(polymost1TexSamplerLoc, 0);
glUniform1i(polymost1PalSwapSamplerLoc, 1);
@ -1760,16 +1894,22 @@ void uploadtexture(int32_t doalloc, vec2_t siz, int32_t texfmt,
}
}
void uploadtextureindexed(int32_t doalloc, vec2_t siz, intptr_t tile)
void uploadtextureindexed(int32_t doalloc, vec2_t offset, vec2_t siz, intptr_t tile)
{
// don't use mipmaps for indexed color textures
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
if (doalloc & 1)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, siz.y, siz.x, 0, GL_RED, GL_UNSIGNED_BYTE, (void*) tile);
}
else
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, siz.y, siz.x, GL_RED, GL_UNSIGNED_BYTE, (void*) tile);
{
glTexSubImage2D(GL_TEXTURE_2D, 0, offset.x, offset.y, siz.y, siz.x, GL_RED, GL_UNSIGNED_BYTE, (void*) tile);
}
}
void uploadbasepalette(int32_t basepalnum)
@ -1806,6 +1946,7 @@ void uploadbasepalette(int32_t basepalnum)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, basepalWFullBrightInfo);
@ -1840,6 +1981,7 @@ void uploadpalswap(int32_t palookupnum)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, PALSWAP_TEXTURE_SIZE, PALSWAP_TEXTURE_SIZE, 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
@ -1915,38 +2057,44 @@ static void gloadtile_art_indexed(int32_t dapic, int32_t dameth, pthtyp *pth, in
//POGOTODO: if !glinfo.texnpot, then we could allocate a texture of the pow2 size, and then populate the subportion using buffersubdata func
//if (!glinfo.texnpot)
Tile tile = {};
if (waloff[dapic])
{
if (doalloc)
char tileIsPacked = tilepacker_getTile(dapic+1, &tile);
if (tileIsPacked &&
tile.rect.width == (uint32_t) tsizart.y &&
tile.rect.height == (uint32_t) tsizart.x)
{
pth->glpic = tilesheetTexIDs[tile.tilesheetID];
doalloc = false;
}
else if (doalloc)
{
glGenTextures(1, (GLuint *)&pth->glpic);
}
glBindTexture(GL_TEXTURE_2D,pth->glpic);
glBindTexture(GL_TEXTURE_2D, pth->glpic);
uploadtextureindexed(doalloc, siz, waloff[dapic]);
if (doalloc)
{
const GLuint clamp_mode = glinfo.clamptoedge ? GL_CLAMP_TO_EDGE : GL_CLAMP;
if (!(dameth & DAMETH_CLAMPED))
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp_if_tile_is_sky(dapic, clamp_mode));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
else
{
// For sprite textures, clamping looks better than wrapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp_mode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp_mode);
}
}
uploadtextureindexed(doalloc, {(int32_t) tile.rect.u, (int32_t) tile.rect.v}, siz, waloff[dapic]);
}
else
{
//Force invalid textures to draw something - an almost purely transparency texture
//This allows the Z-buffer to be updated for mirrors (which are invalidated textures)
pth->glpic = blankTextureID;
}
const GLuint clamp_mode = glinfo.clamptoedge ? GL_CLAMP_TO_EDGE : GL_CLAMP;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
if (!(dameth & DAMETH_CLAMPED))
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp_if_tile_is_sky(dapic, clamp_mode));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
else
{
// For sprite textures, clamping looks better than wrapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp_mode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp_mode);
tilepacker_getTile(0, &tile);
pth->glpic = tilesheetTexIDs[tile.tilesheetID];
}
pth->picnum = dapic;
@ -2122,7 +2270,7 @@ void gloadtile_art(int32_t dapic, int32_t dapal, int32_t tintpalnum, int32_t das
}
if (doalloc) glGenTextures(1,(GLuint *)&pth->glpic); //# of textures (make OpenGL allocate structure)
glBindTexture(GL_TEXTURE_2D,pth->glpic);
glBindTexture(GL_TEXTURE_2D, pth->glpic);
fixtransparency(pic,tsiz,siz,dameth);
@ -2438,7 +2586,7 @@ int32_t gloadtile_hi(int32_t dapic,int32_t dapalnum, int32_t facen, hicreplctyp
if ((doalloc&3)==1)
glGenTextures(1, &pth->glpic); //# of textures (make OpenGL allocate structure)
glBindTexture(GL_TEXTURE_2D,pth->glpic);
glBindTexture(GL_TEXTURE_2D, pth->glpic);
fixtransparency(pic,tsiz,siz,dameth);
@ -2642,12 +2790,13 @@ static void polymost2_drawVBO(GLenum mode,
}
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, (pth && pth->flags & PTH_HASFULLBRIGHT && r_fullbrights) ? pth->ofb->glpic : blankTextureID);
//POGO: temporarily swapped out blankTextureID for 0 (as the blank texture has been moved into the dynamic tilesheets)
glBindTexture(GL_TEXTURE_2D, (pth && pth->flags & PTH_HASFULLBRIGHT && r_fullbrights) ? pth->ofb->glpic : 0);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, pth ? pth->glpic : blankTextureID);
polymost_bindPth(pth);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
@ -2903,8 +3052,9 @@ static void polymost_drawpoly(vec2f_t const * const dpxy, int32_t const n, int32
// just submit the geometry and don't mess with textures.
if (videoGetRenderMode() == REND_POLYMOST)
{
glBindTexture(GL_TEXTURE_2D, pth ? pth->glpic : blankTextureID);
polymost_bindPth(pth);
//POGOTODO: I could move this into bindPth
if (pth && !(pth->flags & PTH_INDEXED))
polymost_usePaletteIndexing(false);
@ -7324,7 +7474,7 @@ void polymost_fillpolygon(int32_t npoints)
glEnable(GL_ALPHA_TEST);
glEnable(GL_TEXTURE_2D);
pthtyp *pth = our_texcache_fetch(DAMETH_NOMASK | (videoGetRenderMode() == REND_POLYMOST && r_useindexedcolortextures ? PTH_INDEXED : 0));
glBindTexture(GL_TEXTURE_2D, pth ? pth->glpic : blankTextureID);
polymost_bindPth(pth);
if (pth && !(pth->flags & PTH_INDEXED))
{
polymost_usePaletteIndexing(false);
@ -7403,7 +7553,7 @@ int32_t polymost_drawtilescreen(int32_t tilex, int32_t tiley, int32_t wallnum, i
loadedhitile[wallnum>>3] |= (1<<(wallnum&7));
usehightile = ousehightile;
glBindTexture(GL_TEXTURE_2D, pth ? pth->glpic : blankTextureID);
polymost_bindPth(pth);
if (pth && !(pth->flags & PTH_INDEXED))
{
polymost_usePaletteIndexing(false);
@ -7531,8 +7681,9 @@ int32_t polymost_printext256(int32_t xpos, int32_t ypos, int16_t col, int16_t ba
if (videoGetRenderMode() < REND_POLYMOST || !in3dmode() || (!polymosttext && gen_font_glyph_tex() < 0))
return -1;
else
glBindTexture(GL_TEXTURE_2D, polymosttext);
glBindTexture(GL_TEXTURE_2D, polymosttext);
polymost_setTexturePosSize({0, 0, 1, 1});
polymost_usePaletteIndexing(false);

View File

@ -0,0 +1,358 @@
/*
* tilepacker.cpp
* A k-d tree based bin packer that organizes rectangular tiles to fit
* neatly into one texture.
*
* Copyright © 2018, Alex Dawson. All rights reserved.
*/
#include "compat.h"
#include "build.h"
#include "tilepacker.h"
typedef struct TreeNode
{
struct TreeNode *pParent, *pChild0, *pChild1;
TileRect rect;
uint32_t maxSide;
uint32_t tileUID;
} TreeNode;
// allocate all the memory we could ever need up front to avoid dynamic allocation
#define NUM_NON_ROOT_NODES MAXPACKEDTILES*2
#define NUM_NODES NUM_NON_ROOT_NODES+MAXTILESHEETS
TreeNode nodes[NUM_NODES];
uint32_t heapNodes = 0;
uint32_t nextTreeNodeIndex = NUM_NON_ROOT_NODES-1;
Tile tiles[MAXPACKEDTILES];
// node rejection queue implemented using a circular buffer
#define MAX_REJECTS (MAXPACKEDTILES-1)
TreeNode rejectQueue[MAX_REJECTS];
uint32_t rejectQueueHeadIndex = 0;
uint32_t numRejected = 0;
static void maxheap_bubbleUp(uint32_t nodeIndex)
{
while (true)
{
if (nodeIndex == 0)
{
return;
}
uint32_t parentIndex = (nodeIndex-1)/2;
if (nodes[parentIndex].maxSide >= nodes[nodeIndex].maxSide)
{
return;
}
// bubble up
TreeNode temp = nodes[nodeIndex];
nodes[nodeIndex] = nodes[parentIndex];
nodes[parentIndex] = temp;
nodeIndex = parentIndex;
}
}
static void maxheap_bubbleDown(uint32_t nodeIndex)
{
while (true)
{
uint32_t largestChildIndex = 2*nodeIndex + 1;
if (largestChildIndex >= heapNodes)
{
return;
}
uint32_t rightChildIndex = largestChildIndex+1;
if (rightChildIndex < heapNodes &&
nodes[rightChildIndex].maxSide > nodes[largestChildIndex].maxSide)
{
largestChildIndex = rightChildIndex;
}
if (nodes[largestChildIndex].maxSide <= nodes[nodeIndex].maxSide)
{
return;
}
// bubble down
TreeNode temp = nodes[nodeIndex];
nodes[nodeIndex] = nodes[largestChildIndex];
nodes[largestChildIndex] = temp;
nodeIndex = largestChildIndex;
}
}
static TreeNode* maxheap_pop()
{
if (heapNodes == 0)
{
return NULL;
}
// swap the root and the last node
TreeNode temp = nodes[0];
nodes[0] = nodes[heapNodes-1];
nodes[heapNodes-1] = temp;
--heapNodes;
maxheap_bubbleDown(0);
return nodes+heapNodes;
}
static TreeNode* maxheap_reserveNode(TreeNode *pParent,
TreeNode *pChild0,
TreeNode *pChild1,
TileRect rectangle,
uint32_t tileUID)
{
if (heapNodes == nextTreeNodeIndex+1)
{
// our tree and heap are going to collide
#ifdef DEBUGGINGAIDS
OSD_Printf("tilepacker: maxheap_reserveNode(): out of nodes\n");
#endif
return NULL;
}
nodes[heapNodes] = {(TreeNode*) pParent,
(TreeNode*) pChild0,
(TreeNode*) pChild1,
rectangle,
rectangle.width >= rectangle.height ? rectangle.width : rectangle.height,
tileUID};
++heapNodes;
return nodes+heapNodes-1;
}
static TreeNode* kdtree_reserveNode(TreeNode *pParent,
TreeNode *pChild0,
TreeNode *pChild1,
TileRect rectangle,
uint32_t tileUID)
{
if (nextTreeNodeIndex == heapNodes-1)
{
// our tree and heap are going to collide
#ifdef DEBUGGINGAIDS
OSD_Printf("tilepacker: kdtree_reserveNode(): out of nodes\n");
#endif
return NULL;
}
nodes[nextTreeNodeIndex] = {(TreeNode*) pParent,
(TreeNode*) pChild0,
(TreeNode*) pChild1,
rectangle,
rectangle.width >= rectangle.height ? rectangle.width : rectangle.height,
tileUID};
--nextTreeNodeIndex;
return nodes+nextTreeNodeIndex+1;
}
static char kdtree_add(uint32_t treeIndex, TreeNode *pNode)
{
TreeNode *pCurrentNode = nodes+NUM_NODES-treeIndex-1;
while (true)
{
// is this node large enough to contain the currentNode?
if (pCurrentNode->rect.width >= pNode->rect.width &&
pCurrentNode->rect.height >= pNode->rect.height)
{
if (pCurrentNode->tileUID != (uint32_t) -1)
{
// if we're not a leaf node, continue to tunnel down until we reach a leaf on the 0-side of the tree
pCurrentNode = pCurrentNode->pChild0;
continue;
}
// otherwise, we have the node we want to split to insert our new node
break;
}
// climb out until we find an unexplored 1-side branch to tunnel down
TreeNode *lastNode;
do
{
lastNode = pCurrentNode;
pCurrentNode = pCurrentNode->pParent;
if (pCurrentNode == NULL)
{
// we've fully explored the tree and asked for the root's parent
return false;
}
} while (pCurrentNode->pChild1 == lastNode || !pCurrentNode->pChild1);
pCurrentNode = pCurrentNode->pChild1;
}
// assign the empty leaf node the tileUID we want to add, then create children to split the node's remaining space
pCurrentNode->tileUID = pNode->tileUID;
tiles[pNode->tileUID].tilesheetID = treeIndex;
tiles[pNode->tileUID].rect = {pCurrentNode->rect.u,
pCurrentNode->rect.v,
pNode->rect.width,
pNode->rect.height};
uint32_t rightSideWidth = pCurrentNode->rect.width - pNode->rect.width;
uint32_t bottomSideHeight = pCurrentNode->rect.height - pNode->rect.height;
TileRect rect0 = {pCurrentNode->rect.u+pNode->rect.width, pCurrentNode->rect.v, rightSideWidth, pCurrentNode->rect.height};
TileRect rect1 = {pCurrentNode->rect.u, pCurrentNode->rect.v+pNode->rect.height, pNode->rect.width, bottomSideHeight};
// decide which way to split
if (rightSideWidth < bottomSideHeight)
{
//POGOTODO: instead of creating two new children and having the tree contain filled nodes
// I should usurp the place of pCurrentNode with the largest child,
// since the smaller child can always be contained within the larger one.
// This requires an additional sort/bubbling step and always splitting width/height
// based on level rather than max in order for the organization to work.
// we'll have a more width-confined space to our right, so chop it off horizontally
// rather than create a tall, potentially narrow empty area to have to fill later
rect0 = {pCurrentNode->rect.u, pCurrentNode->rect.v+pNode->rect.height, pCurrentNode->rect.width, bottomSideHeight};
rect1 = {pCurrentNode->rect.u+pNode->rect.width, pCurrentNode->rect.v, rightSideWidth, pNode->rect.height};
}
// it's important that the larger area become child0
pCurrentNode->pChild0 = kdtree_reserveNode(pCurrentNode,
NULL,
NULL,
rect0,
-1);
pCurrentNode->pChild1 = kdtree_reserveNode(pCurrentNode,
NULL,
NULL,
rect1,
-1);
return true;
}
static char rejectQueue_add(TreeNode *pNode)
{
if (numRejected >= MAX_REJECTS)
{
return false;
}
rejectQueue[(rejectQueueHeadIndex+numRejected) % MAX_REJECTS] = *pNode;
++numRejected;
return true;
}
static TreeNode* rejectQueue_remove()
{
if (numRejected == 0)
{
return NULL;
}
--numRejected;
TreeNode* pNode = rejectQueue+rejectQueueHeadIndex;
rejectQueueHeadIndex = (rejectQueueHeadIndex+1) % MAX_REJECTS;
return pNode;
}
/*static void tilepacker_deleteTree()
{
//POGOTODO: this
}*/
void tilepacker_initTilesheet(uint32_t tilesheetID, uint32_t tilesheetWidth, uint32_t tilesheetHeight)
{
//POGOTODO: delete the tree if it's already been initialized
nodes[NUM_NODES-tilesheetID-1] = {(TreeNode*) 0,
(TreeNode*) 0,
(TreeNode*) 0,
{0, 0, tilesheetWidth, tilesheetHeight},
tilesheetWidth >= tilesheetHeight ? tilesheetWidth : tilesheetHeight,
(uint32_t) -1}; // use the maximum uint32_t to indicate this node is empty space
}
void tilepacker_addTile(uint32_t tileUID, uint32_t tileWidth, uint32_t tileHeight)
{
if (nextTreeNodeIndex < heapNodes)
{
// cannot reserve any more tiles!
#ifdef DEBUGGINGAIDS
OSD_Printf("tilepacker: tilepacker_addTile(): out of nodes\n");
#endif
return;
}
if (tileWidth == 0 ||
tileHeight == 0)
{
// don't allow adding tiles with a width or height of 0
return;
}
maxheap_reserveNode((TreeNode*) 0,
(TreeNode*) 0,
(TreeNode*) 0,
{0, 0, tileWidth, tileHeight},
tileUID);
maxheap_bubbleUp(heapNodes-1);
}
char tilepacker_pack(uint32_t tilesheetID)
{
if (tilesheetID >= MAXTILESHEETS)
{
return false;
}
for (uint32_t numLeft = numRejected; numLeft > 0; --numLeft)
{
TreeNode *pNode = rejectQueue_remove();
char success = kdtree_add(tilesheetID, pNode);
if (!success)
{
rejectQueue_add(pNode);
}
}
for (TreeNode *pNode = maxheap_pop(); pNode != NULL; pNode = maxheap_pop())
{
char success = kdtree_add(tilesheetID, pNode);
if (!success)
{
rejectQueue_add(pNode);
}
}
return numRejected == 0;
}
void tilepacker_discardRejects()
{
numRejected = 0;
}
char tilepacker_getTile(uint32_t tileUID, Tile *pOutput)
{
if (tileUID >= MAXPACKEDTILES)
{
return false;
}
Tile tile = tiles[tileUID];
if (tile.rect.width == 0)
{
// that tileUID has not been packed or didn't fit
return false;
}
if (pOutput)
{
*pOutput = tile;
}
return true;
}

View File

@ -1017,6 +1017,7 @@ int32_t polymost_voxdraw(voxmodel_t *m, const uspritetype *tspr)
glBindTexture(GL_TEXTURE_2D, m->texid[globalpal]);
polymost_usePaletteIndexing(false);
polymost_setTexturePosSize({ 0.f, 0.f, 1.f, 1.f });
glBegin(GL_QUADS); // {{{