commit 3be9600a1252f2f766230cfab77bba09878c552c Author: Justin Marshall Date: Fri Sep 27 07:04:44 2019 +0100 Initial commit. diff --git a/code/renderer/MegaTexture.cpp b/code/renderer/MegaTexture.cpp new file mode 100644 index 0000000..b627986 --- /dev/null +++ b/code/renderer/MegaTexture.cpp @@ -0,0 +1,318 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +idCVar idMegaTexture::r_megaTextureLevel( "r_megaTextureLevel", "0", CVAR_RENDERER | CVAR_INTEGER, "draw only a specific level" ); +idCVar idMegaTexture::r_showMegaTexture( "r_showMegaTexture", "0", CVAR_RENDERER | CVAR_BOOL, "display all the level images" ); +idCVar idMegaTexture::r_showMegaTextureLabels( "r_showMegaTextureLabels", "0", CVAR_RENDERER | CVAR_BOOL, "draw colored blocks in each tile" ); +idCVar idMegaTexture::r_skipMegaTexture( "r_skipMegaTexture", "0", CVAR_RENDERER | CVAR_INTEGER, "only use the lowest level image" ); +idCVar idMegaTexture::r_terrainScale( "r_terrainScale", "3", CVAR_RENDERER | CVAR_INTEGER, "vertically scale USGS data" ); +/* +==================== +idMegaTexture::idMegaTexture +==================== +*/ +idMegaTexture::idMegaTexture() { + albedoLitMegaTextureFile = nullptr; +} + +/* +==================== +idMegaTexture::~idMegaTexture +==================== +*/ +idMegaTexture::~idMegaTexture() { + if (albedoLitMegaTextureFile != nullptr) { + delete albedoLitMegaTextureFile; + albedoLitMegaTextureFile = nullptr; + } +} + +/* +==================== +InitFromMegaFile +==================== +*/ +bool idMegaTexture::InitFromMegaFile( const char *fileBase ) { + idStr name = "megaTextures/"; + idStr megaMaskName; + + name += fileBase; + name.StripFileExtension(); + megaMaskName = name; + + name += ".mega"; + megaMaskName += "_mask.tga"; + + // Open the mega mask image. + megaNormalMaskImage = globalImages->ImageFromFile(megaMaskName.c_str(), TF_LINEAR, TR_REPEAT, TD_DIFFUSE, CF_2D); + if (megaNormalMaskImage == nullptr) { + common->Printf("idMegaTexture: failed to open mega mask %s\n", megaMaskName.c_str()); + return false; + } + + // Load in the megatexture file. + albedoLitMegaTextureFile = rvmMegaTextureFile::LoadMegaTextureFile(name); + if (albedoLitMegaTextureFile == nullptr) { + return false; + } + + currentTriMapping = NULL; + + // force first bind to load everything + currentViewOrigin[0] = -99999999.0f; + currentViewOrigin[1] = -99999999.0f; + currentViewOrigin[2] = -99999999.0f; + + return true; +} + +/* +==================== +SetMappingForSurface + +analyzes xyz and st to create a mapping +This is not very robust, but works for rectangular grids +==================== +*/ +void idMegaTexture::SetMappingForSurface( const srfTriangles_t *tri ) { + if ( tri == currentTriMapping ) { + return; + } + currentTriMapping = tri; + + if ( !tri->verts ) { + return; + } +// jmarshall - heavily modified this, basically all we care about here is the min/max of the terrain. + surfaceBounds = tri->bounds; +// jmarshall end +} + +/* +==================== +BindForViewOrigin +==================== +*/ +void idMegaTexture::BindForViewOrigin( const idVec3 viewOrigin ) { + + SetViewOrigin( viewOrigin ); + + albedoLitMegaTextureFile->BindForViewOrigin(viewOrigin); + + // Bind the mega normal mask image. + GL_SelectTexture(7); + megaNormalMaskImage->Bind(); +} + +/* +==================== +Unbind + +This can go away once everything uses fragment programs so the enable states don't +need tracking +==================== +*/ +void idMegaTexture::Unbind( void ) { + for ( int i = 0 ; i < MAX_LEVELS; i++ ) { + GL_SelectTexture( 1+i ); + globalImages->BindNull(); + } +} + + +/* +==================== +SetViewOrigin +==================== +*/ +void idMegaTexture::SetViewOrigin( const idVec3 viewOrigin ) { + if ( r_showMegaTextureLabels.IsModified() ) { + r_showMegaTextureLabels.ClearModified(); + currentViewOrigin[0] = viewOrigin[0] + 0.1; // force a change + albedoLitMegaTextureFile->Invalidate(); + } + + if ( viewOrigin == currentViewOrigin ) { + return; + } + if ( r_skipMegaTexture.GetBool() ) { + return; + } + + currentViewOrigin = viewOrigin; +// jmarshall + float texCenter[2]; + + // convert the viewOrigin to a texture center, which will + // be a different conversion for each megaTexture + //for ( int i = 0 ; i < 2 ; i++ ) { + // texCenter[i] = + // viewOrigin[0] * localViewToTextureCenter[i][0] + + // viewOrigin[1] * localViewToTextureCenter[i][1] + + // viewOrigin[2] * localViewToTextureCenter[i][2] + + // localViewToTextureCenter[i][3]; + //} + + // TexCenter should be the normalized position between the min and max based on the player position. + idVec3 center = surfaceBounds.GetCenter(); + idVec3 megaViewOrigin = currentViewOrigin - center; + idBounds megaBounds; + megaBounds[0] = surfaceBounds[0] - center; + megaBounds[1] = surfaceBounds[1] - center; + + // Normalize megaViewOrigin between megabounds. + texCenter[0] = ((megaViewOrigin[0] - megaBounds[0][0]) / (megaBounds[1][0] - megaBounds[0][0])); + texCenter[1] = ((megaViewOrigin[1] - megaBounds[0][1]) / (megaBounds[1][1] - megaBounds[0][1])); + + albedoLitMegaTextureFile->UpdateForCenter(texCenter); +// jmarshall end +} + + +/* +==================== +UpdateTile + +A local tile will only be mapped to globalTile[ localTile + X * TILE_PER_LEVEL ] for some x +==================== +*/ +void idTextureLevel::UpdateTile( int localX, int localY, int globalX, int globalY ) { + idTextureTile *tile = &tileMap[localX][localY]; + + if ( tile->x == globalX && tile->y == globalY ) { + return; + } + if ( (globalX & (TILE_PER_LEVEL-1)) != localX || (globalY & (TILE_PER_LEVEL-1)) != localY ) { + common->Error( "idTextureLevel::UpdateTile: bad coordinate mod" ); + } + + tile->x = globalX; + tile->y = globalY; + + byte data[ TILE_SIZE * TILE_SIZE ]; + + if ( globalX >= tilesWide || globalX < 0 || globalY >= tilesHigh || globalY < 0 ) { + // off the map + memset( data, 0, sizeof( data ) ); + } else { + // extract the data from the full image (FIXME: background load from disk) + int tileNum = tileOffset + tile->y * tilesWide + tile->x; + + mega->ReadTile(data, tileNum); + } + + if ( idMegaTexture::r_showMegaTextureLabels.GetBool() ) { + // put a color marker in it + byte color[4] = { 255 * localX / TILE_PER_LEVEL, 255 * localY / TILE_PER_LEVEL, 0, 0 }; + for ( int x = 0 ; x < 8 ; x++ ) { + for ( int y = 0 ; y < 8 ; y++ ) { + *(int *)&data[ ( ( y + TILE_SIZE/2 - 4 ) * TILE_SIZE + x + TILE_SIZE/2 - 4 ) ] = *(int *)color; + } + } + } + + // upload all the mip-map levels + int level = 0; + int size = TILE_SIZE; + glCompressedTexSubImage2D(GL_TEXTURE_2D, level, localX * size, localY * size, size, size, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, size * size, data); +} + +/* +==================== +UpdateForCenter + +Center is in the 0.0 to 1.0 range +==================== +*/ +void idTextureLevel::UpdateForCenter( float center[2] ) { + int globalTileCorner[2]; + int localTileOffset[2]; + + if ( tilesWide <= TILE_PER_LEVEL && tilesHigh <= TILE_PER_LEVEL ) { + globalTileCorner[0] = 0; + globalTileCorner[1] = 0; + localTileOffset[0] = 0; + localTileOffset[1] = 0; + // orient the mask so that it doesn't mask anything at all + parms[0] = 0.25; + parms[1] = 0.25; + parms[3] = 0.25; + } else { + for ( int i = 0 ; i < 2 ; i++ ) { + float global[2]; + + // this value will be outside the 0.0 to 1.0 range unless + // we are in the corner of the megaTexture + global[i] = ( center[i] * parms[3] - 0.5 ) * TILE_PER_LEVEL; + + globalTileCorner[i] = (int)( global[i] + 0.5 ); + + localTileOffset[i] = globalTileCorner[i] & (TILE_PER_LEVEL-1); + + // scaling for the mask texture to only allow the proper window + // of tiles to show through + parms[i] = -globalTileCorner[i] / (float)TILE_PER_LEVEL; + } + } + + image->Bind(); + + for ( int x = 0 ; x < TILE_PER_LEVEL ; x++ ) { + for ( int y = 0 ; y < TILE_PER_LEVEL ; y++ ) { + int globalTile[2]; + + globalTile[0] = globalTileCorner[0] + ( ( x - localTileOffset[0] ) & (TILE_PER_LEVEL-1) ); + globalTile[1] = globalTileCorner[1] + ( ( y - localTileOffset[1] ) & (TILE_PER_LEVEL-1) ); + + UpdateTile( x, y, globalTile[0], globalTile[1] ); + } + } +} + +/* +===================== +Invalidate + +Forces all tiles to be regenerated +===================== +*/ +void idTextureLevel::Invalidate() { + for ( int x = 0 ; x < TILE_PER_LEVEL ; x++ ) { + for ( int y = 0 ; y < TILE_PER_LEVEL ; y++ ) { + tileMap[x][y].x = + tileMap[x][y].y = -99999; + } + } +} diff --git a/code/renderer/MegaTexture.h b/code/renderer/MegaTexture.h new file mode 100644 index 0000000..771ce4c --- /dev/null +++ b/code/renderer/MegaTexture.h @@ -0,0 +1,195 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +struct rvmMegaTextureSourceFile_t +{ + rvmMegaTextureSourceFile_t() + { + scratch = nullptr; + scratch_position = 0; + file = nullptr; + } + ~rvmMegaTextureSourceFile_t() + { + if (scratch) + { + delete scratch; + scratch = nullptr; + } + if (file) + { + fileSystem->CloseFile(file); + file = nullptr; + } + + scratch_position = 0; + } + + void AllocScratch(int len) + { + scratch = new byte[len]; + scratch_position = 0; + } + + void ResetScratch() + { + scratch_position = 0; + } + + byte ReadByte() + { + return scratch[scratch_position++]; + } + + idFile *file; + int columns; + int rows; + int fileSize; + int numBytes; + byte *scratch; + int scratch_position; + + TargaHeader targa_header; +}; + +class idTextureTile { +public: + int x, y; +}; + +static const int TILE_PER_LEVEL = 4; +static const int MAX_MEGA_CHANNELS = 3; // normal, diffuse, specular +static const int MAX_LEVELS = 12; +static const int MAX_LEVEL_WIDTH = 1024; +static const int TILE_SIZE = MAX_LEVEL_WIDTH / TILE_PER_LEVEL; + +class idMegaTexture; +class rvmMegaTextureFile; + +class idTextureLevel { +public: + rvmMegaTextureFile *mega; + + int tileOffset; + int tilesWide; + int tilesHigh; + + idImage *image; + idTextureTile tileMap[TILE_PER_LEVEL][TILE_PER_LEVEL]; + + float parms[4]; + + void UpdateForCenter( float center[2] ); + void UpdateTile( int localX, int localY, int globalX, int globalY ); + void Invalidate(); +}; + +typedef struct { + int tileSize; + int tilesWide; + int tilesHigh; +} megaTextureHeader_t; + +// jmarshall +class rvmMegaTextureFile { +public: + ~rvmMegaTextureFile(); + + // Loads in a mega texture file. + static rvmMegaTextureFile *LoadMegaTextureFile(const char *name); + + void UpdateForCenter(float texCenter[2]); + void BindForViewOrigin(const idVec3 viewOrigin); // binds images and sets program parameters + void Invalidate(void); + + void ReadTile(byte *tileBuffer, int tileNum); +public: + int numLevels; + idTextureLevel levels[MAX_LEVELS]; // 0 is the highest resolution + megaTextureHeader_t header; +private: + rvmMegaTextureFile(); + + idFile *fileHandle; +}; +// jmarshall end + +class idMegaTexture { +public: + idMegaTexture(); + ~idMegaTexture(); + + bool InitFromMegaFile( const char *fileBase ); + void SetMappingForSurface( const srfTriangles_t *tri ); // analyzes xyz and st to create a mapping + void BindForViewOrigin( const idVec3 origin ); // binds images and sets program parameters + void Unbind(); // removes texture bindings + + static void MakeMegaTexture_f( const idCmdArgs &args ); +private: + friend class idTextureLevel; +// jmarshall + friend class rvmMegaTextureFile; +// jmarshall end + void SetViewOrigin( const idVec3 origin ); + static void GenerateMegaMipMaps( megaTextureHeader_t *header, idFile *file ); + static void GenerateMegaPreview( const char *fileName ); +// jmarshall + static void ProcessTGABlock(rvmMegaTextureSourceFile_t *file, byte *targa_rgba, TargaHeader &targa_header, int columns, int rows, int scale); + static idFile *LoadTGA(const char *name, TargaHeader &targa_header, int &columns, int &rows, int &fileSize, int &numBytes); +// jmarshall end + + const srfTriangles_t *currentTriMapping; + + idVec3 currentViewOrigin; +// jmarshall +// float localViewToTextureCenter[2][4]; + idBounds surfaceBounds; +// jmarshall end + +// jmarshall + idImage *megaNormalMaskImage; +// jmarshall end + +// jmarshall + rvmMegaTextureFile *albedoLitMegaTextureFile; +// jmarshall end + + + static idCVar r_megaTextureLevel; + static idCVar r_showMegaTexture; + static idCVar r_showMegaTextureLabels; + static idCVar r_skipMegaTexture; + static idCVar r_terrainScale; + +// jmarshall + static idCVar r_megatexture_ambient; +// jmarshall end +}; + diff --git a/code/renderer/MegaTextureBuild.cpp b/code/renderer/MegaTextureBuild.cpp new file mode 100644 index 0000000..d33fe6c --- /dev/null +++ b/code/renderer/MegaTextureBuild.cpp @@ -0,0 +1,587 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "DXT/DXTCodec.h" +#include "Color/ColorSpace.h" + +// https://gist.github.com/Alhadis/455346f35286745d8562d24a8764dfa1 +#define ChannelBlend_Add(B,L) ((byte)(min(255, (B + L)))) +#define ChannelBlend_Multiply(A,B) ((byte)((A * B) / 255)) + +idCVar idMegaTexture::r_megatexture_ambient("r_megatexture_ambient", "20", CVAR_RENDERER | CVAR_INTEGER, "amount of lighting to add to the lit megatexture during building"); + +static byte ReadByte(idFile *f) { + byte b; + + f->Read(&b, 1); + return b; +} + +static short ReadShort(idFile *f) { + byte b[2]; + + f->Read(&b, 2); + + return b[0] + (b[1] << 8); +} + +int RoundDownToPowerOfTwo(int num) { + int pot; + for (pot = 1; (pot * 2) <= num; pot <<= 1) { + } + return pot; +} + +int R_GetTargaBPP(TargaHeader &header) { + switch (header.pixel_size) + { + case 8: + return 1; + + case 24: + return 3; + + case 32: + return 4; + } + common->FatalError("Mega GetTargetBPP: Unknown BPP\n"); +} + +/* +==================== +GenerateMegaMipMaps +==================== +*/ +void idMegaTexture::GenerateMegaMipMaps(megaTextureHeader_t *header, idFile *outFile) { + outFile->Flush(); + + // out fileSystem doesn't allow read / write access... + idFile *inFile = fileSystem->OpenFileRead(outFile->GetName()); + + int tileOffset = 1; + int width = header->tilesWide; + int height = header->tilesHigh; + + int tileSize = header->tileSize * header->tileSize * 4; + int tileSizeCompressed = header->tileSize * header->tileSize; + byte *oldBlock = (byte *)_alloca(tileSize); + byte *oldBlockCompressed = (byte *)_alloca(tileSizeCompressed); + byte *newBlock = (byte *)_alloca(tileSize); + byte *newBlockCompressed = (byte *)_alloca(tileSizeCompressed); + + while (width > 1 || height > 1) { + int newHeight = (height + 1) >> 1; + if (newHeight < 1) { + newHeight = 1; + } + int newWidth = (width + 1) >> 1; + if (width < 1) { + width = 1; + } + common->Printf("generating %i x %i block mip level\n", newWidth, newHeight); + + int tileNum; + + for (int y = 0; y < newHeight; y++) { + common->Printf("row %i\n", y); + session->UpdateScreen(); + + for (int x = 0; x < newWidth; x++) { + // mip map four original blocks down into a single new block + for (int yy = 0; yy < 2; yy++) { + for (int xx = 0; xx < 2; xx++) { + int tx = x * 2 + xx; + int ty = y * 2 + yy; + + if (tx > width || ty > height) { + // off edge, zero fill + memset(newBlock, 0, sizeof(newBlock)); + } + else { + tileNum = tileOffset + ty * width + tx; + inFile->Seek(tileNum * tileSizeCompressed, FS_SEEK_SET); + inFile->Read(oldBlockCompressed, tileSizeCompressed); + + idDxtDecoder decoder; + decoder.DecompressYCoCgDXT5(oldBlockCompressed, oldBlock, TILE_SIZE, TILE_SIZE); + //idColorSpace::ConvertCoCg_YToRGB(oldBlockCompressed, oldBlockCompressed, tileSize, tileSize); + + // R_WriteTGA("test.tga", (const byte *)oldBlock, TILE_SIZE, TILE_SIZE); + } + // mip map the new pixels + for (int yyy = 0; yyy < TILE_SIZE / 2; yyy++) { + for (int xxx = 0; xxx < TILE_SIZE / 2; xxx++) { + byte *in = &oldBlock[(yyy * 2 * TILE_SIZE + xxx * 2) * 4]; + byte *out = &newBlock[(((TILE_SIZE / 2 * yy) + yyy) * TILE_SIZE + (TILE_SIZE / 2 * xx) + xxx) * 4]; + out[0] = (in[0] + in[4] + in[0 + TILE_SIZE * 4] + in[4 + TILE_SIZE * 4]) >> 2; + out[1] = (in[1] + in[5] + in[1 + TILE_SIZE * 4] + in[5 + TILE_SIZE * 4]) >> 2; + out[2] = (in[2] + in[6] + in[2 + TILE_SIZE * 4] + in[6 + TILE_SIZE * 4]) >> 2; + out[3] = (in[3] + in[7] + in[3 + TILE_SIZE * 4] + in[7 + TILE_SIZE * 4]) >> 2; + } + } + + // write the block out + tileNum = tileOffset + width * height + y * newWidth + x; + + + idDxtEncoder encoder; + //idColorSpace::ConvertRGBToCoCg_Y(newBlock, newBlock, TILE_SIZE, TILE_SIZE); + encoder.CompressYCoCgDXT5Fast((const byte *)newBlock, newBlockCompressed, TILE_SIZE, TILE_SIZE); + outFile->Seek(tileNum * tileSizeCompressed, FS_SEEK_SET); + outFile->Write(newBlockCompressed, tileSizeCompressed); + } + } + } + } + tileOffset += width * height; + width = newWidth; + height = newHeight; + } + + delete inFile; +} + +/* +==================== +GenerateMegaPreview + +Make a 2k x 2k preview image for a mega texture that can be used in modeling programs +==================== +*/ +void idMegaTexture::GenerateMegaPreview(const char *fileName) { + idFile *fileHandle = fileSystem->OpenFileRead(fileName); + if (!fileHandle) { + common->Printf("idMegaTexture: failed to open %s\n", fileName); + return; + } + + idStr outName = fileName; + outName.StripFileExtension(); + outName += "_preview.tga"; + + common->Printf("Creating %s.\n", outName.c_str()); + + megaTextureHeader_t header; + + fileHandle->Read(&header, sizeof(header)); + if (header.tileSize < 64 || header.tilesWide < 1 || header.tilesHigh < 1) { + common->Printf("idMegaTexture: bad header on %s\n", fileName); + return; + } + + int tileSize = header.tileSize; + int width = header.tilesWide; + int height = header.tilesHigh; + int tileOffset = 1; + int tileBytes = tileSize * tileSize; + // find the level that fits + while (width * tileSize > 2048 || height * tileSize > 2048) { + tileOffset += width * height; + width >>= 1; + if (width < 1) { + width = 1; + } + height >>= 1; + if (height < 1) { + height = 1; + } + } + + byte *pic = (byte *)R_StaticAlloc(width * height * (tileBytes * 4)); + byte *oldBlock = (byte *)_alloca(tileBytes); + byte *oldBlockUncompressed = (byte *)R_StaticAlloc(tileSize * tileSize * 4); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int tileNum = tileOffset + y * width + x; + fileHandle->Seek(tileNum * tileBytes, FS_SEEK_SET); + fileHandle->Read(oldBlock, tileBytes); + + idDxtDecoder decoder; + decoder.DecompressYCoCgDXT5(oldBlock, oldBlockUncompressed, tileSize, tileSize); + idColorSpace::ConvertCoCg_YToRGB(oldBlockUncompressed, oldBlockUncompressed, tileSize, tileSize); + + for (int yy = 0; yy < tileSize; yy++) { + memcpy(pic + ((y * tileSize + yy) * width * tileSize + x * tileSize) * 4, + oldBlockUncompressed + yy * tileSize * 4, tileSize * 4); + } + } + } + + + + R_WriteTGA(outName.c_str(), pic, width * tileSize, height * tileSize, false); + + R_StaticFree(oldBlockUncompressed); + R_StaticFree(pic); + + delete fileHandle; +} + +/* +==================== +idMegaTexture::LoadTGA +==================== +*/ +idFile *idMegaTexture::LoadTGA(const char *name, TargaHeader &targa_header, int &columns, int &rows, int &fileSize, int &numBytes) +{ + // + // open the file + // + common->Printf("Opening %s.\n", name); + fileSize = fileSystem->ReadFile(name, NULL, NULL); + idFile *file = fileSystem->OpenFileRead(name); + + if (!file) { + common->Printf("Couldn't open %s\n", name); + return nullptr; + } + + targa_header.id_length = ReadByte(file); + targa_header.colormap_type = ReadByte(file); + targa_header.image_type = ReadByte(file); + + targa_header.colormap_index = ReadShort(file); + targa_header.colormap_length = ReadShort(file); + targa_header.colormap_size = ReadByte(file); + targa_header.x_origin = ReadShort(file); + targa_header.y_origin = ReadShort(file); + targa_header.width = ReadShort(file); + targa_header.height = ReadShort(file); + targa_header.pixel_size = ReadByte(file); + targa_header.attributes = ReadByte(file); + + if (targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3) { + common->Error("LoadTGA( %s ): Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n", name); + } + + if (targa_header.colormap_type != 0) { + common->Error("LoadTGA( %s ): colormaps not supported\n", name); + } + + if ((targa_header.pixel_size != 32 && targa_header.pixel_size != 24) && targa_header.image_type != 3) { + common->Error("LoadTGA( %s ): Only 32 or 24 bit images supported (no colormaps)\n", name); + } + + //if (targa_header.image_type == 2 || targa_header.image_type == 3) { + // numBytes = targa_header.width * targa_header.height * (targa_header.pixel_size >> 3); + // if (numBytes > fileSize - 18 - targa_header.id_length) { + // common->Error("LoadTGA( %s ): incomplete file\n", name); + // } + //} + + columns = targa_header.width; + rows = targa_header.height; + + // skip TARGA image comment + if (targa_header.id_length != 0) { + file->Seek(targa_header.id_length, FS_SEEK_CUR); + } + + return file; +} + + +/* +==================== +idMegaTexture::ProcessTGABlock +==================== +*/ +void idMegaTexture::ProcessTGABlock(rvmMegaTextureSourceFile_t *file, byte *targa_rgba, TargaHeader &targa_header, int columns, int rows, int scale) +{ + int row, column; + byte *pixbuf; + byte *startRowPixBuf; + + if (targa_header.image_type == 2 || targa_header.image_type == 3) { + // Uncompressed RGB or gray scale image + for (row = 0; row < TILE_SIZE * scale; row++) { + pixbuf = targa_rgba + row * (columns * scale) * 4; + startRowPixBuf = pixbuf; + + for (column = 0; column < columns; column++) { + unsigned char red, green, blue, alphabyte; + switch (targa_header.pixel_size) { + case 8: + blue = file->ReadByte(); + green = blue; + red = blue; + + for (int f = 0; f < scale; f++) + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + } + break; + + case 24: + blue = file->ReadByte(); + green = file->ReadByte(); + red = file->ReadByte(); + for (int f = 0; f < scale; f++) + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + } + break; + case 32: + blue = file->ReadByte(); + green = file->ReadByte(); + red = file->ReadByte(); + alphabyte = file->ReadByte(); + for (int f = 0; f < scale; f++) + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + } + break; + default: + common->Error("LoadTGA: illegal pixel_size '%d'\n", targa_header.pixel_size); + break; + } + } + + // If we are scaling, just duplicate the row! + int scale_loop = scale; + while (scale_loop > 1) + { + int64_t rowlength = pixbuf - startRowPixBuf; + + memcpy(pixbuf, startRowPixBuf, rowlength); + pixbuf += rowlength; + + row++; + scale_loop--; + } + } + } + else if (targa_header.image_type == 10) { // Runlength encoded RGB images + // jmarshall: I'm not supporting RLE so we can pre-load as much as the image as possible off disc during baking. + common->FatalError("MegaTexture ProcessTGABlock: RLE not supported!"); + } +} + +/* +==================== +MakeMegaTexture_f + +Incrementally load a giant tga file and process into the mega texture block format +==================== +*/ +void idMegaTexture::MakeMegaTexture_f(const idCmdArgs &args) { + rvmMegaTextureSourceFile_t albedoSource, litSource; + + if (args.Argc() != 2) { + common->Printf("USAGE: makeMegaTexture \n"); + return; + } + + idStr name = "megaTextures/"; + name += args.Argv(1); + name.StripFileExtension(); + name += ".tga"; + + idStr albedoName = "megagen/bin/"; + albedoName += args.Argv(1); + albedoName.StripFileExtension(); + albedoName += ".tga"; + + idStr lit_name = "megaTextures/"; + lit_name += args.Argv(1); + lit_name.StripFileExtension(); + lit_name += "_lit.tga"; + + albedoSource.file = idMegaTexture::LoadTGA(albedoName, albedoSource.targa_header, albedoSource.columns, albedoSource.rows, albedoSource.fileSize, albedoSource.numBytes); + if (albedoSource.file == nullptr) + return; + + litSource.file = idMegaTexture::LoadTGA(lit_name, litSource.targa_header, litSource.columns, litSource.rows, litSource.fileSize, litSource.numBytes); + if (litSource.file == nullptr) + return; + + megaTextureHeader_t mtHeader; + + mtHeader.tileSize = TILE_SIZE; + mtHeader.tilesWide = RoundDownToPowerOfTwo(albedoSource.targa_header.width) / TILE_SIZE; + mtHeader.tilesHigh = RoundDownToPowerOfTwo(albedoSource.targa_header.height) / TILE_SIZE; + + idStr outName = name; + outName.StripFileExtension(); + outName += ".mega"; + + common->Printf("Writing %i x %i size %i tiles to %s.\n", mtHeader.tilesWide, mtHeader.tilesHigh, mtHeader.tileSize, outName.c_str()); + + // open the output megatexture file + idFile *out = fileSystem->OpenFileWrite(outName.c_str()); + + out->Write(&mtHeader, sizeof(mtHeader)); + out->Seek(TILE_SIZE * TILE_SIZE, FS_SEEK_SET); + + // we will process this one row of tiles at a time, since the entire thing + // won't fit in memory + byte *targa_rgba = (byte *)R_StaticAlloc(TILE_SIZE * albedoSource.targa_header.width * 4); + byte *targa_compose = (byte *)R_StaticAlloc(TILE_SIZE * albedoSource.targa_header.width * 4); + + int len = (TILE_SIZE * TILE_SIZE + 1) * 4; + byte *megaMemory = (byte *)Mem_Alloc(len); + //idFile_Memory *megaMemoryTile = new idFile_Memory("mega_memory", (char *)megaMemory, len); + + int currentMegaTilePosition = 0; + byte *megaMemoryTile = new byte[len]; + + byte *compressed_tile_buffer = (byte *)R_StaticAlloc(TILE_SIZE * TILE_SIZE); + + int albedoSourcebpp = R_GetTargaBPP(albedoSource.targa_header); + int litSourcebpp = R_GetTargaBPP(litSource.targa_header); + + int albedoSourceLen = (albedoSource.columns * albedoSourcebpp); + albedoSourceLen = albedoSourceLen * TILE_SIZE; + + int litSourceLen = (litSource.columns * litSourcebpp); + litSourceLen = litSourceLen * TILE_SIZE; + + albedoSource.AllocScratch(albedoSourceLen); + + int numLitBlocksToSkip = albedoSource.targa_header.width / litSource.targa_header.width; + int litSkippedBlocks = 0; + + // Lit source will contain numLitBlocksToSkip if we are scaling! + litSource.AllocScratch(litSourceLen); + byte *targa_lit = new byte[((TILE_SIZE* numLitBlocksToSkip) * albedoSource.targa_header.width) * 4]; + + int blockRowsRemaining = mtHeader.tilesHigh; + while (blockRowsRemaining--) { + common->Printf("%i blockRowsRemaining\n", blockRowsRemaining); + session->UpdateScreen(); + + // Do a single big read here, small byte reads off disc are just slow. + albedoSource.file->Read(albedoSource.scratch, albedoSourceLen); + + // Process the lit and albedo source images. + albedoSource.ResetScratch(); + ProcessTGABlock(&albedoSource, targa_rgba, albedoSource.targa_header, albedoSource.columns, albedoSource.rows, 1); + + + // Only load litSource if we need another block. + if (numLitBlocksToSkip == 1 || litSkippedBlocks == 0) + { + litSource.file->Read(litSource.scratch, litSourceLen); + + litSource.ResetScratch(); + ProcessTGABlock(&litSource, targa_lit, litSource.targa_header, litSource.columns, litSource.rows, numLitBlocksToSkip); + } + + // This is to support MegaLight not outputing lightmaps 1:1 with the size of the megatexture albedo. + int lightmap_start = ((TILE_SIZE * albedoSource.targa_header.width)) * litSkippedBlocks; //litSourceLen * litSkippedBlocks; + for (int i = 0; i < TILE_SIZE * albedoSource.targa_header.width; i++) + { + int lightmapPosition = lightmap_start + i; + + byte litR = ChannelBlend_Add(targa_lit[(lightmapPosition * 4) + 0], r_megatexture_ambient.GetInteger()); + byte litG = ChannelBlend_Add(targa_lit[(lightmapPosition * 4) + 1], r_megatexture_ambient.GetInteger()); + byte litB = ChannelBlend_Add(targa_lit[(lightmapPosition * 4) + 2], r_megatexture_ambient.GetInteger()); + + targa_compose[(i * 4) + 0] = ChannelBlend_Multiply(targa_rgba[(i * 4) + 0], litR); + targa_compose[(i * 4) + 1] = ChannelBlend_Multiply(targa_rgba[(i * 4) + 1], litG); + targa_compose[(i * 4) + 2] = ChannelBlend_Multiply(targa_rgba[(i * 4) + 2], litB); + targa_compose[(i * 4) + 3] = 255; + } + + if (litSkippedBlocks >= numLitBlocksToSkip - 1) { + litSkippedBlocks = 0; + } + else { + litSkippedBlocks++; + } + + // + // write out individual blocks from the full row block buffer + // + for (int rowBlock = 0; rowBlock < mtHeader.tilesWide; rowBlock++) { + idDxtEncoder encoder; + + //megaMemoryTile->Seek(0, FS_SEEK_SET); + currentMegaTilePosition = 0; + for (int y = 0; y < TILE_SIZE; y++) { + memcpy(&megaMemoryTile[currentMegaTilePosition], targa_compose + (y * albedoSource.targa_header.width + rowBlock * TILE_SIZE) * 4, TILE_SIZE * 4); + currentMegaTilePosition += TILE_SIZE * 4; + } + + int fileWriteAddress = out->Tell(); + + // convert the image data to YCoCg and use the YCoCgDXT5 compressor + idColorSpace::ConvertRGBToCoCg_Y((byte *)megaMemoryTile, (byte *)megaMemoryTile, TILE_SIZE, TILE_SIZE); + + encoder.CompressYCoCgDXT5Fast_Generic((const byte *)megaMemoryTile, compressed_tile_buffer, TILE_SIZE, TILE_SIZE); + + //{ + // idDxtDecoder decoder; + // byte *uncompress_test_me = (byte *)_alloca(TILE_SIZE * TILE_SIZE * 4); + // + // decoder.DecompressImageDXT5(compressed_tile_buffer, uncompress_test_me, TILE_SIZE, TILE_SIZE); + // R_WriteTGA("test_compression.tga", uncompress_test_me, TILE_SIZE, TILE_SIZE); + //} + + out->Write(compressed_tile_buffer, TILE_SIZE * TILE_SIZE); + } + } + + delete megaMemoryTile; + delete targa_lit; + + R_StaticFree(targa_rgba); + R_StaticFree(targa_compose); + R_StaticFree(compressed_tile_buffer); + + GenerateMegaMipMaps(&mtHeader, out); + + delete out; + delete albedoSource.file; + delete litSource.file; + + GenerateMegaPreview(outName.c_str()); +#if 0 + if ((targa_header.attributes & (1 << 5))) { // image flp bit + R_VerticalFlip(*pic, *width, *height); + } +#endif + +} + + diff --git a/code/renderer/MegaTextureFile.cpp b/code/renderer/MegaTextureFile.cpp new file mode 100644 index 0000000..700ec5d --- /dev/null +++ b/code/renderer/MegaTextureFile.cpp @@ -0,0 +1,245 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" + +#include "tr_local.h" + +static union { + int intVal; + byte color[4]; +} fillColor; + +static byte colors[8][4] = { + { 0, 0, 0, 255 }, + { 255, 0, 0, 255 }, + { 0, 255, 0, 255 }, + { 255, 255, 0, 255 }, + { 0, 0, 255, 255 }, + { 255, 0, 255, 255 }, + { 0, 255, 255, 255 }, + { 255, 255, 255, 255 } +}; + +/* +=========================== +rvmMegaTextureFile::rvmMegaTextureFile +=========================== +*/ +rvmMegaTextureFile::rvmMegaTextureFile() +{ + fileHandle = nullptr; + numLevels = 0; +} + +/* +=========================== +rvmMegaTextureFile::~rvmMegaTextureFile +=========================== +*/ +rvmMegaTextureFile::~rvmMegaTextureFile() { + if (fileHandle != nullptr) { + fileSystem->CloseFile(fileHandle); + fileHandle = nullptr; + } +} + +/* +=========================== +rvmMegaTextureFile::LoadMegaTextureFile +=========================== +*/ +rvmMegaTextureFile *rvmMegaTextureFile::LoadMegaTextureFile(const char *name) { + int width, height; + rvmMegaTextureFile *megaTextureFile = new rvmMegaTextureFile(); + + megaTextureFile->fileHandle = fileSystem->OpenFileRead(name); + if (!megaTextureFile->fileHandle) { + common->Printf("rvmMegaTextureFile: failed to open %s\n", name); + delete megaTextureFile; + return nullptr; + } + + megaTextureFile->fileHandle->Read(&megaTextureFile->header, sizeof(megaTextureFile->header)); + if (megaTextureFile->header.tileSize < 64 || megaTextureFile->header.tilesWide < 1 || megaTextureFile->header.tilesHigh < 1) { + common->Printf("idMegaTexture: bad header on %s\n", name); + delete megaTextureFile; + return nullptr; + } + + megaTextureFile->numLevels = 0; + width = megaTextureFile->header.tilesWide; + height = megaTextureFile->header.tilesHigh; + + int tileOffset = 1; // just past the header + + memset(megaTextureFile->levels, 0, sizeof(levels)); + while (1) { + idTextureLevel *level = &megaTextureFile->levels[megaTextureFile->numLevels]; + + level->mega = megaTextureFile; + level->tileOffset = tileOffset; + level->tilesWide = width; + level->tilesHigh = height; + level->parms[0] = -1; // initially mask everything + level->parms[1] = 0; + level->parms[2] = 0; + level->parms[3] = (float)width / (float)TILE_PER_LEVEL; + level->Invalidate(); + + tileOffset += level->tilesWide * level->tilesHigh; + + char str[1024]; + sprintf(str, "_mega_%i", megaTextureFile->numLevels); + + // give each level a default fill color + for (int i = 0; i < 4; i++) { + fillColor.color[i] = colors[megaTextureFile->numLevels + 1][i]; + } + + idImageOpts opts; + opts.format = FMT_DXT5; + opts.colorFormat = CFM_DEFAULT; + opts.gammaMips = 0; + opts.width = MAX_LEVEL_WIDTH; + opts.height = MAX_LEVEL_WIDTH; + opts.textureType = TT_2D; + opts.isPersistant = true; + opts.numMSAASamples = 0; + opts.numLevels = 1; + + idTempArray data(MAX_LEVEL_WIDTH * MAX_LEVEL_WIDTH * 4); + + for (int i = 0; i < MAX_LEVEL_WIDTH * MAX_LEVEL_WIDTH; i++) { + ((int *)data.Ptr())[i] = fillColor.intVal; + } + + megaTextureFile->levels[megaTextureFile->numLevels].image = globalImages->ScratchImage(str, &opts, TF_LINEAR, TR_REPEAT, TD_DIFFUSE); + megaTextureFile->levels[megaTextureFile->numLevels].image->UploadScratch(data.Ptr(), MAX_LEVEL_WIDTH, MAX_LEVEL_WIDTH); + megaTextureFile->numLevels++; + + if (width <= TILE_PER_LEVEL && height <= TILE_PER_LEVEL) { + break; + } + width = (width + 1) >> 1; + height = (height + 1) >> 1; + } + + return megaTextureFile; +} + +/* +======================== +rvmMegaTextureFile::ReadTile +======================== +*/ +void rvmMegaTextureFile::ReadTile(byte *tileBuffer, int tileNum) { + int tileSize = TILE_SIZE * TILE_SIZE; + + fileHandle->Seek(tileNum * tileSize, FS_SEEK_SET); + //memset(data, 128, sizeof(data)); + fileHandle->Read(tileBuffer, tileSize); +} + +/* +=========================== +rvmMegaTextureFile::UpdateForCenter +=========================== +*/ +void rvmMegaTextureFile::UpdateForCenter(float texCenter[2]) { + for (int i = 0; i < numLevels; i++) { + levels[i].UpdateForCenter(texCenter); + } +} +/* +=========================== +rvmMegaTextureFile::Invalidate +=========================== +*/ +void rvmMegaTextureFile::Invalidate(void) { + for (int i = 0; i < numLevels; i++) { + levels[i].Invalidate(); + } +} + +/* +=========================== +rvmMegaTextureFile::BindForViewOrigin +=========================== +*/ +void rvmMegaTextureFile::BindForViewOrigin(const idVec3 viewOrigin) { + // borderClamp image goes in texture 0 + GL_SelectTexture(0); + globalImages->borderClampImage->Bind(); + + // level images in higher textures, blurriest first + for (int i = 0; i < 7; i++) { + GL_SelectTexture(1 + i); + + if (i >= numLevels) { + globalImages->whiteImage->Bind(); + + static float parms[4] = { -2, -2, 0, 1 }; // no contribution + //glProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i, parms ); + renderProgManager.SetUniformValue((const renderParm_t)(RENDERPARM_MEGALEVEL0 + i), parms); + } + else { + idTextureLevel *level = &levels[numLevels - 1 - i]; + + if (idMegaTexture::r_showMegaTexture.GetBool()) { + if (i & 1) { + globalImages->blackImage->Bind(); + } + else { + globalImages->whiteImage->Bind(); + } + } + else { + level->image->Bind(); + } + //glProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i, level->parms ); + renderProgManager.SetUniformValue((const renderParm_t)(RENDERPARM_MEGALEVEL0 + i), level->parms); + } + } + + float parms[4]; + parms[0] = 0; + parms[1] = 0; + parms[2] = 0; + parms[3] = 1; + renderProgManager.SetUniformValue((const renderParm_t)(RENDERPARM_MEGALEVEL0 + 7), parms); + //glProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 7, parms ); + + //parms[0] = 1; + //parms[1] = 1; + //parms[2] = r_terrainScale.GetFloat(); + //parms[3] = 1; + //glProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 8, parms ); +} \ No newline at end of file diff --git a/code/tools/compilers/megagen/MegaGen.cpp b/code/tools/compilers/megagen/MegaGen.cpp new file mode 100644 index 0000000..36fe233 --- /dev/null +++ b/code/tools/compilers/megagen/MegaGen.cpp @@ -0,0 +1,195 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "MegaGen.h" + +/* +=============================================== + +MegaGen Tool + +Builds the megatexture source. Why? Because doing it in Photoshop causes computers to explode :). + +=============================================== +*/ + +/* +=================== +WriteTargaHeader +=================== +*/ +void WriteTargaHeader(idFile *megaTarga, int width, int height) +{ + byte buffer[18]; + int i; + + memset(buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 32; // pixel size + + megaTarga->Write(buffer, sizeof(buffer)); +} + +/* +=================== +LerpPixel +=================== +*/ +byte LerpPixel(byte start, byte end, byte mask) { + float t = ((float)mask) / 255.0f; + + return (byte)((1 - t)*start + t * end); +} + +/* +=================== +R_GetMegaLayerImagePixel +=================== +*/ +byte *R_GetMegaLayerImagePixel(MegaLayerImage_t *layer, int x, int y) { + return &layer->data[(y * layer->width * 4) + (x * 4)]; +} + +/* +=================== +EvaluateMegaLayer +=================== +*/ +void EvaluateMegaLayer(int megaSize, int megaScanLine, MegaLayer *megaLayer, byte *megaScratch) { + float maskScanlineScale = (float)megaScanLine / (float)megaSize; + + for (int i = 0; i < megaSize; i++) + { + int albedoX = i % megaLayer->albedoImage.width; + int albedoY = megaScanLine % megaLayer->albedoImage.height; + + float maskColumnScale = (float)i / (float)megaSize; + + int maskX = maskColumnScale * (float)megaLayer->maskImage.width; + int maskY = maskScanlineScale * (float)megaLayer->maskImage.height; + + byte *megaLayerAlbedoData = R_GetMegaLayerImagePixel(&megaLayer->albedoImage, albedoX, albedoY); + byte *megaLayerMaskData = R_GetMegaLayerImagePixel(&megaLayer->maskImage, maskX, maskY); + + byte mask = megaLayerMaskData[0]; + + // Lerp MegaPixel against the current megaScalinePixel based on the mask. + megaScratch[(i * 4) + 0] = LerpPixel(megaScratch[(i * 4) + 0], megaLayerAlbedoData[2], mask); + megaScratch[(i * 4) + 1] = LerpPixel(megaScratch[(i * 4) + 1], megaLayerAlbedoData[1], mask); + megaScratch[(i * 4) + 2] = LerpPixel(megaScratch[(i * 4) + 2], megaLayerAlbedoData[0], mask); + megaScratch[(i * 4) + 3] = LerpPixel(megaScratch[(i * 4) + 3], megaLayerAlbedoData[3], mask); + } +} + +/* +=================== +BuildMegaProject +=================== +*/ +void BuildMegaProject(int megaSize, idFile *megaTarga, rvmMegaProject &megaProject) { + idTempArray megaTextureScanLine(megaSize * 4); + idTempArray scratchScanLine(megaSize * 4); + + // Write out the targa header + WriteTargaHeader(megaTarga, megaSize, megaSize); + + // Walk through each scanline for evaluation. + for (int i = 0; i < megaSize; i++) + { + //common->Printf("Writing scanline %d/%d\n", i, megaSize); + + // Iterate over all the layers. + for (int layerId = 0; layerId < megaProject.GetNumMegaLayers(); layerId++) + { + // Evaluate the mega layer. + EvaluateMegaLayer(megaSize, i, megaProject.GetMegaLayer(layerId), scratchScanLine.Ptr()); + } + + megaTarga->Write(scratchScanLine.Ptr(), megaSize * 4); + } +} + +/* +=================== +RunMegaGen_f +=================== +*/ +void RunMegaGen_f(const idCmdArgs &args) { + rvmMegaProject megaProject; + + common->SetRefreshOnPrint(true); + + if (args.Argc() < 3) { + common->Warning("Usage: megagen \n"); + common->SetRefreshOnPrint(false); + return; + } + + idStr megaProjectName = va("megagen/%s.megagen", args.Argv(1)); + idStr megaTargaName = va("megagen/bin/%s.tga", args.Argv(1)); + + idFileScoped megaTarga(fileSystem->OpenFileWrite(megaTargaName)); + + int megaSize = atoi(args.Argv(2)); + + idParser parser; + + // Read the entire mega project off disk. + if (!parser.LoadFile(megaProjectName)) + { + common->Warning("Failed to load megaproject %s\n", megaProjectName.c_str()); + common->SetRefreshOnPrint(false); + return; + } + + parser.SetFlags(DECL_LEXER_FLAGS); + + // Parse the mega project. + if (!megaProject.ParseProject(parser)) + { + // ParseProject should return what went wrong. + common->SetRefreshOnPrint(false); + return; + } + + // Build it!!!! + BuildMegaProject(megaSize, megaTarga, megaProject); + + common->Printf("MegaProject built successfully!"); + + common->SetRefreshOnPrint(false); +} \ No newline at end of file diff --git a/code/tools/compilers/megagen/MegaGen.h b/code/tools/compilers/megagen/MegaGen.h new file mode 100644 index 0000000..c263e45 --- /dev/null +++ b/code/tools/compilers/megagen/MegaGen.h @@ -0,0 +1,90 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#pragma once + +// +// MegaLayerImage_t +// +struct MegaLayerImage_t { + MegaLayerImage_t() + { + width = -1; + height = -1; + data = nullptr; + } + + ~MegaLayerImage_t() + { + if (data != nullptr) + { + Mem_Free(data); + data = nullptr; + } + } + + int width; + int height; + byte *data; +}; + +// +// MegaLayer +// +struct MegaLayer { + MegaLayerImage_t albedoImage; + MegaLayerImage_t maskImage; +}; + +// +// rvmMegaProject +// +class rvmMegaProject { +public: + rvmMegaProject(); + ~rvmMegaProject(); + + // Parsers the project. + bool ParseProject(idParser &parser); + + // Returns the megalayer given idx. + MegaLayer *GetMegaLayer(int idx) { return megaLayers[idx]; } + + // Returns the number of mega layers in the project. + int GetNumMegaLayers(void) const { return megaLayers.Num(); } +private: + // Parses each layer. + bool ParseLayer(MegaLayer *layer, idStr &layerStr); + +private: + idList megaLayers; +}; + +void R_LoadTGA(const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp); diff --git a/code/tools/compilers/megagen/MegaProject.cpp b/code/tools/compilers/megagen/MegaProject.cpp new file mode 100644 index 0000000..2a3dfcf --- /dev/null +++ b/code/tools/compilers/megagen/MegaProject.cpp @@ -0,0 +1,169 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). +Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "precompiled.h" + +#include "MegaGen.h" + +/* +====================== +rvmMegaProject::rvmMegaProject +====================== +*/ +rvmMegaProject::rvmMegaProject() +{ + +} + +/* +====================== +rvmMegaProject::~rvmMegaProject +====================== +*/ +rvmMegaProject::~rvmMegaProject() +{ + for (int i = 0; i < megaLayers.Num(); i++) + { + delete megaLayers[i]; + } + megaLayers.Clear(); +} + +/* +====================== +rvmMegaProject::ParseLayer +====================== +*/ +bool rvmMegaProject::ParseLayer(MegaLayer *layer, idStr &layerStr) { + idParser parser; + idToken token; + + if (!parser.LoadMemory(layerStr, layerStr.Size(), "MegaLayer")) + return false; + + parser.SetFlags(DECL_LEXER_FLAGS); + + if (!parser.ExpectTokenString("{")) { + common->Warning("MegaLayer expected opening {"); + return false; + } + + // Parse until EOF + while (true) + { + if (parser.EndOfFile()) + { + common->Warning("Unexpected EOF in MegaLayer!"); + return false; + } + + parser.ReadToken(&token); + + if (token == "}") + { + break; + } + + // Load in the albedo image for this layer. + if (token == "albedo") + { + parser.ReadToken(&token); + R_LoadTGA(token, &layer->albedoImage.data, &layer->albedoImage.width, &layer->albedoImage.height, nullptr); + if (layer->albedoImage.data == NULL) + { + common->Warning("Failed to load albedo image %s\n", token.c_str()); + return false; + } + } + else if (token == "mask") + { + parser.ReadToken(&token); + R_LoadTGA(token, &layer->maskImage.data, &layer->maskImage.width, &layer->maskImage.height, nullptr); + if (layer->maskImage.data == NULL) + { + common->Warning("Failed to load albedo image %s\n", token.c_str()); + return false; + } + } + else + { + common->Warning("While parsing layer, unknown token %s\n", token.c_str()); + return false; + } + } + + return true; +} + +/* +====================== +rvmMegaProject::ParseProject +====================== +*/ +bool rvmMegaProject::ParseProject(idParser &parser) { + int numLayers; + + // Check how many layers we have. + if (!parser.ExpectTokenString("numlayers")) + { + parser.Warning("Expected numlayers token"); + return false; + } + numLayers = parser.ParseInt(); + + // Parse the layers. + for (int i = 0; i < numLayers; i++) + { + MegaLayer *megaLayer = new MegaLayer(); + idStr layerStr; + + // Check the layer name. + if (!parser.ExpectTokenString(va("layer_%d", i))) + { + parser.Warning("Layer name invalid or unexpected token!"); + return false; + } + + // Extract the text in the braced section. + parser.ParseBracedSectionExact(layerStr); + + // Parse the layer. + if (!ParseLayer(megaLayer, layerStr)) + { + parser.Warning("Failed to parse megalayer"); + return false; + } + + // Add the layer to the list. + megaLayers.Append(megaLayer); + } + + return true; +} \ No newline at end of file diff --git a/dkbase/megagen/test_box.megagen b/dkbase/megagen/test_box.megagen new file mode 100644 index 0000000..f04ccd8 --- /dev/null +++ b/dkbase/megagen/test_box.megagen @@ -0,0 +1,13 @@ +numlayers 2 + +layer_0 +{ + albedo megagen/test/dirt_albedo.tga + mask megagen/white.tga +} + +layer_1 +{ + albedo megagen/test/stones_albedo.tga + mask megagen/test_box/mountain_layer.tga +} \ No newline at end of file diff --git a/dkbase/megagen/test_box/mountain_layer.tga b/dkbase/megagen/test_box/mountain_layer.tga new file mode 100644 index 0000000..dd20824 Binary files /dev/null and b/dkbase/megagen/test_box/mountain_layer.tga differ diff --git a/dkbase/megagen/white.tga b/dkbase/megagen/white.tga new file mode 100644 index 0000000..b3f0a5b Binary files /dev/null and b/dkbase/megagen/white.tga differ diff --git a/dkbase/renderprogs/megatexture.pixel b/dkbase/renderprogs/megatexture.pixel new file mode 100644 index 0000000..4b220b2 --- /dev/null +++ b/dkbase/renderprogs/megatexture.pixel @@ -0,0 +1,124 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +uniform sampler2D samp0 : register(s0); +uniform sampler2D samp1 : register(s1); +uniform sampler2D samp2 : register(s2); +uniform sampler2D samp3 : register(s3); +uniform sampler2D samp4 : register(s4); +uniform sampler2D samp5 : register(s5); +uniform sampler2D samp6 : register(s6); +uniform sampler2D samp7 : register(s7); + +struct PS_IN { + float4 position : VPOS; + float2 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; + float2 texcoord2 : TEXCOORD2; + float2 texcoord3 : TEXCOORD3; + float2 texcoord4 : TEXCOORD4; + float2 texcoord5 : TEXCOORD5; + float2 texcoord6 : TEXCOORD6; + float4 texcoord7 : TEXCOORD7; +}; + +struct PS_OUT { + float4 color : COLOR; +}; + +void main( PS_IN fragment, out PS_OUT result ) { + float4 mask1, mask2; + float4 invMask1, invMask2; + float4 combined; + float2 scaled; + float4 R2; + + mask1.x = tex2D(samp0, fragment.texcoord0.xy).x; + mask1.y = tex2D(samp0, fragment.texcoord1.xy).x; + mask1.z = tex2D(samp0, fragment.texcoord2.xy).x; + mask1.w = tex2D(samp0, fragment.texcoord3.xy).x; + + mask2.x = tex2D(samp0, fragment.texcoord4.xy).x; + mask2.y = tex2D(samp0, fragment.texcoord5.xy).x; + mask2.z = tex2D(samp0, fragment.texcoord6.xy).x; + mask2.w = 1.0; + + invMask1 = 1 - mask1; + invMask2 = 1 - mask2; + + combined = float4(0, 0, 0, 0); + scaled = fragment.texcoord7.zw; + + // Sample the lowest quality first. + R2 = tex2D(samp1, scaled.xy); + R2.xyz = ConvertYCoCgToRGB(R2); + scaled = scaled * 2; + combined = combined * invMask1.x; + combined = (R2 * mask1.x) + combined; + + R2 = tex2D(samp2, scaled.xy); + R2.xyz = ConvertYCoCgToRGB(R2); + scaled = scaled * 2; + combined = combined * invMask1.y; + combined = (R2 * mask1.y) + combined; + + R2 = tex2D(samp3, scaled.xy); + R2.xyz = ConvertYCoCgToRGB(R2); + scaled = scaled * 2; + combined = combined * invMask1.z; + combined = (R2 * mask1.z) + combined; + + R2 = tex2D(samp4, scaled.xy); + R2.xyz = ConvertYCoCgToRGB(R2); + scaled = scaled * 2; + combined = combined * invMask1.w; + combined = (R2 * mask1.w) + combined; + + R2 = tex2D(samp5, scaled.xy); + R2.xyz = ConvertYCoCgToRGB(R2); + scaled = scaled * 2; + combined = combined * invMask2.x; + combined = (R2 * mask2.x) + combined; + + R2 = tex2D(samp6, scaled.xy); + R2.xyz = ConvertYCoCgToRGB(R2); + scaled = scaled * 2; + combined = combined * invMask2.y; + combined = (R2 * mask2.y) + combined; + + //R2 = tex2D(samp7, scaled.xy); + //R2.xyz = ConvertYCoCgToRGB(R2); + //scaled = scaled * 2; + //combined = combined * invMask2.z; + //combined = (R2 * mask2.z) + combined; + + result.color = combined; +} diff --git a/dkbase/renderprogs/megatexture.vertex b/dkbase/renderprogs/megatexture.vertex new file mode 100644 index 0000000..ad91a7a --- /dev/null +++ b/dkbase/renderprogs/megatexture.vertex @@ -0,0 +1,70 @@ +/* +=========================================================================== + +IcedTech GPL Source Code + +Copyright (C) 2019 Real Vector Math Studios(Justin Marshall). + +This file is part of the IcedTech GPL Source Code ("IcedTech GPL Source Code"). + +IcedTech GPL Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +IcedTech GPL Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTech GPL Source Code. If not, see . + +In addition, the IcedTech GPL Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IcedTech GPL Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "global.inc" + +struct VS_IN { + float4 position : POSITION; + float2 texcoord : TEXCOORD0; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 color : COLOR0; +}; + +struct VS_OUT { + float4 position : POSITION; + float2 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; + float2 texcoord2 : TEXCOORD2; + float2 texcoord3 : TEXCOORD3; + float2 texcoord4 : TEXCOORD4; + float2 texcoord5 : TEXCOORD5; + float2 texcoord6 : TEXCOORD6; + float4 texcoord7 : TEXCOORD7; +}; + +void main( VS_IN vertex, out VS_OUT result ) { + result.position.x = dot4( vertex.position, rpMVPmatrixX ); + result.position.y = dot4( vertex.position, rpMVPmatrixY ); + result.position.z = dot4( vertex.position, rpMVPmatrixZ ); + result.position.w = dot4( vertex.position, rpMVPmatrixW ); + + // Megatexture masks, (texCoord * scale) + offset. + result.texcoord0.xy = (vertex.texcoord * rpMegaTextureLevel0.w) + rpMegaTextureLevel0.xy; + result.texcoord1.xy = (vertex.texcoord * rpMegaTextureLevel1.w) + rpMegaTextureLevel1.xy; + result.texcoord2.xy = (vertex.texcoord * rpMegaTextureLevel2.w) + rpMegaTextureLevel2.xy; + result.texcoord3.xy = (vertex.texcoord * rpMegaTextureLevel3.w) + rpMegaTextureLevel3.xy; + result.texcoord4.xy = (vertex.texcoord * rpMegaTextureLevel4.w) + rpMegaTextureLevel4.xy; + result.texcoord5.xy = (vertex.texcoord * rpMegaTextureLevel5.w) + rpMegaTextureLevel5.xy; + result.texcoord6.xy = (vertex.texcoord * rpMegaTextureLevel6.w) + rpMegaTextureLevel6.xy; + + // Images only have scale. + result.texcoord7.xy = vertex.texcoord * rpMegaTextureLevel6.w; + result.texcoord7.zw = vertex.texcoord; +} \ No newline at end of file diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..a9b13b8 --- /dev/null +++ b/readme.txt @@ -0,0 +1,14 @@ +Some people have been asking for the finished Doom 3 megatexture code, so here it is. + +Things that still need to be done: + ZLib compression. + Multithreading. + +I'm switching over to virtual texturing, so before I remove this code from my codebase, I'm simply archiving it. +This ONLY contains the files related to the Doom 3 MegaTexture implementation I finished. + +This will take some effort to get to compile in either vanilla or BFG(in branch I moved a lot of BFG code over to vanilla). but you will have an easier time putting this into the BFG edition. + +This depot also contains the MegaGen tool I wrote that allows you to build megatextures pretty easily, but it does need work! + +See the megaproject: dkbase/megagen/test_box.megagen