diff --git a/source/build/include/build.h b/source/build/include/build.h index e0d821178..a722880ef 100644 --- a/source/build/include/build.h +++ b/source/build/include/build.h @@ -99,9 +99,6 @@ enum rendmode_t { #define MAXWALLSB ((MAXWALLS>>2)+(MAXWALLS>>3)) -#define MAXTILES 30720 -#define MAXUSERTILES (MAXTILES-16) // reserve 16 tiles at the end - #define MAXVOXELS 1024 #define MAXSTATUS 1024 #define MAXPLAYERS 16 diff --git a/source/common/textures/buildtiles.cpp b/source/common/textures/buildtiles.cpp index d9a0ccf0b..284ebc76d 100644 --- a/source/common/textures/buildtiles.cpp +++ b/source/common/textures/buildtiles.cpp @@ -38,6 +38,10 @@ #include "textures.h" #include "image.h" #include "cache1d.h" +#include "baselayer.h" +#include "palette.h" +#include "m_crc32.h" +#include "build.h" enum { @@ -45,6 +49,16 @@ enum MAXARTFILES_TOTAL = 220 }; +extern char* palookup[]; + +BuildFiles TileFiles; + +//========================================================================== +// +// +// +//========================================================================== + picanm_t tileConvertAnimFormat(int32_t const picanmdisk) { // Unpack a 4 byte packed anim descriptor into the internal 5 byte format. @@ -68,8 +82,16 @@ picanm_t tileConvertAnimFormat(int32_t const picanmdisk) FBitmap FTileTexture::GetBgraBitmap(PalEntry* remap, int* ptrans) { FBitmap bmp; + TArray buffer; bmp.Create(Size.x, Size.y); - const uint8_t* ppix = Get8BitPixels(); // any properly implemented tile MUST return something valid here. + const uint8_t* ppix = Get8BitPixels(); + if (!ppix) + { + // This is needed for tiles with a palette remap. + buffer.Resize(Size.x * Size.y); + Create8BitPixels(buffer.Data()); + ppix = buffer.Data(); + } if (ppix) bmp.CopyPixelData(0, 0, ppix, Size.x, Size.y, Size.y, 1, 0, remap); return bmp; } @@ -87,7 +109,7 @@ void FTileTexture::Create8BitPixels(uint8_t* buffer) // //========================================================================== -FTexture* GetTileTexture(const char* name, const TArray& backingstore, uint32_t offset, int width, int height, int picanm) +FArtTile* GetTileTexture(const char* name, const TArray& backingstore, uint32_t offset, int width, int height, int picanm) { auto tex = new FArtTile(backingstore, offset, width, height, picanm); if (tex) @@ -97,6 +119,20 @@ FTexture* GetTileTexture(const char* name, const TArray& backingstore, return tex; } +//========================================================================== +// +// +// +//========================================================================== + +void BuildFiles::AddTile(int tilenum, FTexture* tex, bool permap) +{ + auto& array = permap ? AllMapTiles : AllTiles; + array.Push(tex); + tiles[tilenum] = tex; + if (!permap) tilesbak[tilenum] = tex; +} + //=========================================================================== // // AddTiles @@ -105,18 +141,24 @@ FTexture* GetTileTexture(const char* name, const TArray& backingstore, // //=========================================================================== -void BuildArtFile::AddTiles () +void BuildFiles::AddTiles (int firsttile, TArray& RawData, bool permap) { const uint8_t *tiles = RawData.Data(); // int numtiles = LittleLong(((uint32_t *)tiles)[1]); // This value is not reliable - int tilestart = LittleLong(((uint32_t *)tiles)[2]); - int tileend = LittleLong(((uint32_t *)tiles)[3]); + int tilestart = LittleLong(((int *)tiles)[2]); + int tileend = LittleLong(((int *)tiles)[3]); const uint16_t *tilesizx = &((const uint16_t *)tiles)[8]; const uint16_t *tilesizy = &tilesizx[tileend - tilestart + 1]; const uint32_t *picanm = (const uint32_t *)&tilesizy[tileend - tilestart + 1]; const uint8_t *tiledata = (const uint8_t *)&picanm[tileend - tilestart + 1]; + if (firsttile != -1) + { + tileend = tileend - tilestart + firsttile; + tilestart = firsttile; + } + for (int i = tilestart; i <= tileend; ++i) { int pic = i - tilestart; @@ -127,11 +169,8 @@ void BuildArtFile::AddTiles () if (width <= 0 || height <= 0) continue; - // This name is mainly for debugging so that there is something more to go by than the mere index. - FStringf name("TILE_%s_%05d_%08x_%04x_%04x", filename, uint32_t(tiledata - tiles), width, height); - auto tex = GetTileTexture(name, RawData, uint32_t(tiledata - tiles), width, height, anm); - BuildTileDescriptor desc = { i, tex }; - Textures.Push(desc); + auto tex = GetTileTexture("", RawData, uint32_t(tiledata - tiles), width, height, anm); + AddTile(i, tex); tiledata += size; } } @@ -144,17 +183,29 @@ void BuildArtFile::AddTiles () // //=========================================================================== -int CountTiles (const void *RawData) +int CountTiles (const char *fn, const uint8_t *RawData) { int version = LittleLong(*(uint32_t *)RawData); if (version != 1) { + initprintf("%s: Invalid art file version. Must be 1, got %d\n", fn, version); return 0; } int tilestart = LittleLong(((uint32_t *)RawData)[2]); int tileend = LittleLong(((uint32_t *)RawData)[3]); + if ((unsigned)tilestart >= MAXUSERTILES || (unsigned)tileend >= MAXUSERTILES) + { + initprintf("%s: Invalid tilestart or tileend\n", fn); + return 0; + } + if (tileend < tilestart) + { + initprintf("%s: tileend < tilestart\n", fn); + return 0; + } + return tileend >= tilestart ? tileend - tilestart + 1 : 0; } @@ -168,7 +219,8 @@ int CountTiles (const void *RawData) void BuildFiles::CloseAllMapArt() { - PerMapArtFiles.Clear(); + AllMapTiles.DeleteAndClear(); + PerMapArtFiles.DeleteAndClear(); } //=========================================================================== @@ -184,7 +236,7 @@ void BuildFiles::CloseAllMapArt() // //=========================================================================== -void BuildFiles::LoadArtFile(const char *fn, bool mapart) +void BuildFiles::LoadArtFile(const char *fn, bool mapart, int firsttile) { auto old = FindFile(fn); if (old >= ArtFiles.Size()) // Do not process if already loaded. @@ -198,51 +250,79 @@ void BuildFiles::LoadArtFile(const char *fn, bool mapart) { if (memcmp(artptr, "BUILDART", 8) == 0) artptr += 8; // Only load the data if the header is present - if (CountTiles(artptr) > 0) + if (CountTiles(fn, artptr) > 0) { auto& descs = mapart ? PerMapArtFiles : ArtFiles; - descs.Reserve(1); - auto& fd = descs.Last(); - fd.filename = fn; - fd.RawData = std::move(artdata); - fd.AddTiles(); + auto file = new BuildArtFile; + descs.Push(file); + file->filename = fn; + file->RawData = std::move(artdata); + AddTiles(firsttile, file->RawData, mapart); } } } + else + { + //initprintf("%s: file not found\n", fn); + } } else { - // Reuse the old one but move it to the top. - auto fd = std::move(ArtFiles[old]); - ArtFiles.Delete(old); - ArtFiles.Push(std::move(fd)); + // Reuse the old one but move it to the top. (better not.) + //auto fd = std::move(ArtFiles[old]); + //ArtFiles.Delete(old); + //ArtFiles.Push(std::move(fd)); } } -static FString artGetIndexedFileName(const char *base, int32_t index) -{ - FString result; - if (index >= MAXARTFILES_BASE) - { - char XX[3] = { char('0' + (index / 10) % 10), char('0' + index % 10), 0 }; - result = base; - result.Substitute("XX", XX); - } - else - { - result.Format(base, index); - } - return result; -} - +//========================================================================== +// +// +// +//========================================================================== void BuildFiles::LoadArtSet(const char* filename) { for (int index = 0; index < MAXARTFILES_BASE; index++) { - auto fn = artGetIndexedFileName(filename, index); + FStringf fn(filename, index); LoadArtFile(fn, false); } } -BuildFiles TileFiles; + +//========================================================================== +// +// Checks if a custom tile has alredy been added to the list. +// For each tile index there may only be one replacement and its +// type may never change! +// +//========================================================================== + +FTexture* BuildFiles::ValidateCustomTile(int tilenum, int type) +{ + if (tilenum < 0 || tilenum >= MAXTILES) return nullptr; + if (tiles[tilenum] != tilesbak[tilenum]) return nullptr; // no mucking around with map tiles. + auto tile = tiles[tilenum]; + if (tile && tile->GetUseType() == type) return tile; // already created + if (tile->GetUseType() > FTexture::Art) return nullptr; // different custom type - cannot replace again. + FTexture* replacement = nullptr; + if (type == FTexture::Writable) + { + replacement = new FWritableTile; + } + else if (type == FTexture::Restorable) + { + // This is for modifying an existing tile. + // It only gets used for the crosshair and two specific effects: + // A) the fire in Blood. + // B) the pin display in Redneck Rampage's bowling lanes. + if (tile->GetWidth() == 0 || tile->GetHeight() == 0) return nullptr; // The base must have a size for this to work. + // todo: invalidate hardware textures for tile. + replacement = new FRestorableTile(tile); + } + else return nullptr; + AddTile(tilenum, replacement); + return replacement; +} + diff --git a/source/common/textures/formats/arttexture.cpp b/source/common/textures/formats/arttexture.cpp index 63c9cb7f0..2f0d75ab7 100644 --- a/source/common/textures/formats/arttexture.cpp +++ b/source/common/textures/formats/arttexture.cpp @@ -66,16 +66,21 @@ public: FImageSource *ArtImage_TryCreate(FileReader & file) { auto buffer = file.Read(); - int32_t const artstatus = artCheckUnitFileHeader(buffer.Data(), buffer.Size()); - if (artstatus < 0) return nullptr; - int32_t picanmdisk; - memcpy(&picanmdisk, &buffer[20], sizeof(int32_t)); - picanmdisk = B_LITTLE32(picanmdisk); - //tileConvertAnimFormat(tile, picanmdisk); + // Cannot load if smaller than the header. + if (buffer.Size() < ARTv1_UNITOFFSET) return nullptr; + uint32_t* header = (uint32_t *) buffer.Data(); + int ver = LittleLong(header[0]); + if (ver != 1) return nullptr; - int Width = B_LITTLE16(B_UNBUF16(&buffer[16])); - int Height = B_LITTLE16(B_UNBUF16(&buffer[18])); + // Only allow files with one tile. + int firsttile = LittleLong(header[1]); + int lasttile = LittleLong(header[2]); + if (firsttile != lasttile) return nullptr; + + int32_t picanmdisk = LittleLong(header[5]); + int Width = LittleShort(B_UNBUF16(&buffer[16])); + int Height = LittleShort(B_UNBUF16(&buffer[18])); if (Width <= 0 || Height <= 0) { diff --git a/source/common/textures/imagehelpers.cpp b/source/common/textures/imagehelpers.cpp index 68670903d..06217afe9 100644 --- a/source/common/textures/imagehelpers.cpp +++ b/source/common/textures/imagehelpers.cpp @@ -43,6 +43,7 @@ namespace ImageHelpers PalEntry BaseColors[256]; PalEntry BasePalette[256]; // same as above, but with a being a proper alpha channel. int WhiteIndex, BlackIndex; + int alphaThreshold; ColorTable256k RGB256k; int BestColor(int r, int g, int b, int first, int num) diff --git a/source/common/textures/imagehelpers.h b/source/common/textures/imagehelpers.h index d06708fd2..04d47a894 100644 --- a/source/common/textures/imagehelpers.h +++ b/source/common/textures/imagehelpers.h @@ -55,6 +55,7 @@ namespace ImageHelpers extern PalEntry BasePalette[256]; // same as above, but with a being a proper alpha channel. extern int WhiteIndex, BlackIndex; extern ColorTable256k RGB256k; + extern int alphaThreshold; // Todo: This should not pick fullbright colors. int BestColor(int r, int g, int b, int first = 0, int num = 255); @@ -74,7 +75,7 @@ namespace ImageHelpers inline uint8_t RGBToPalette(bool wantluminance, int r, int g, int b, int a = 255) { - return a < 128? 255 : RGB256k.RGB[r >> 2][g >> 2][b >> 2]; + return a < alphaThreshold? 255 : RGB256k.RGB[r >> 2][g >> 2][b >> 2]; } inline uint8_t RGBToPalette(bool wantluminance, PalEntry pe, bool hasalpha = true) diff --git a/source/common/textures/imagetexture.cpp b/source/common/textures/imagetexture.cpp index 28fc4b598..7d5b01047 100644 --- a/source/common/textures/imagetexture.cpp +++ b/source/common/textures/imagetexture.cpp @@ -37,6 +37,7 @@ #include "templates.h" #include "bitmap.h" #include "image.h" +#include "imagehelpers.h" //========================================================================== @@ -73,9 +74,19 @@ FBitmap FImageTexture::GetBgraBitmap(PalEntry *p, int *trans) { FBitmap bmp; bmp.Create(Size.x, Size.y); - mImage->CopyPixels(&bmp, 0); // Todo: Handle translations. + if (p == nullptr) + { + mImage->CopyPixels(&bmp, 0); // Todo: Handle translations. + } + else + { + // For different base palettes the image needs to be downconverted. + TArray ppix(Size.x * Size.y, true); + mImage->CreatePalettedPixels(ppix.Data()); + bmp.CopyPixelData(0, 0, ppix.Data(), Size.x, Size.y, Size.y, 1, 0, p); + } return bmp; -} +} //=========================================================================== // @@ -85,5 +96,6 @@ FBitmap FImageTexture::GetBgraBitmap(PalEntry *p, int *trans) void FImageTexture::Create8BitPixels(uint8_t* buffer) { + ImageHelpers::alphaThreshold = alphaThreshold; return mImage->CreatePalettedPixels(buffer); } diff --git a/source/common/textures/textures.h b/source/common/textures/textures.h index 139b6ffcf..40a3b0b98 100644 --- a/source/common/textures/textures.h +++ b/source/common/textures/textures.h @@ -35,6 +35,7 @@ #ifndef __TEXTURES_H #define __TEXTURES_H +#include "compat.h" #include "textureid.h" #include "zstring.h" #include "tarray.h" @@ -69,9 +70,13 @@ enum ECreateTexBufferFlags CTF_CheckOnly = 8, // Only runs the code to get a content ID but does not create a texture. Can be used to access a caching system for the hardware textures. }; -struct size_16_t // must be the same format as vec2_16_t, we cannot include a dirty header like compat.h here. +enum { - int16_t x, y; + //MAXCACHE1DSIZE = (50 * 1024 * 1024), + + MAXTILES = 30720, + MAXUSERTILES = (MAXTILES-16) // reserve 16 tiles at the end + }; // NOTE: If the layout of this struct is changed, loadpics() must be modified @@ -182,6 +187,10 @@ struct FTextureBuffer // Base texture class class FTexture { + friend struct BuildFiles; + friend bool tileLoad(int tileNum); + friend const uint8_t* tilePtr(int num); + public: enum UseType : uint8_t { @@ -214,16 +223,21 @@ public: int GetWidth() const { return Size.x; } int GetHeight() const { return Size.y; } - const size_16_t &GetSize() const { return Size; } + const vec2_16_t &GetSize() const { return Size; } const uint8_t& GetPicSize() const { return PicSize; } int GetLeftOffset() const { return PicAnim.xofs; } int GetTopOffset() const { return PicAnim.yofs; } picanm_t& GetAnim() { return PicAnim; } // This must be modifiable. There's quite a bit of code messing around with the flags in here. + rottile_t& GetRotTile() { return RotTile; } FTextureBuffer CreateTexBuffer(int translation, int flags = 0); bool GetTranslucency(); void CheckTrans(unsigned char * buffer, int size, int trans); bool ProcessData(unsigned char * buffer, int w, int h, bool ispatch); virtual void Reload() {} + UseType GetUseType() const { return useType; } + + int alphaThreshold = 128; + picanm_t PicAnim = {}; protected: @@ -259,8 +273,7 @@ protected: FString Name; - size_16_t Size = { 0,0 }; // Keep this in the native format so that we can use it without copying it around. - picanm_t PicAnim = {}; + vec2_16_t Size = { 0,0 }; // Keep this in the native format so that we can use it without copying it around. rottile_t RotTile = { -1,-1 }; uint8_t bMasked = true; // Texture (might) have holes int8_t bTranslucent = -1; // Does this texture have an active alpha channel? @@ -269,12 +282,12 @@ protected: UseType useType = Image; PalEntry FloorSkyColor; PalEntry CeilingSkyColor; + intptr_t CacheHandle = 0; // For tiles that do not have a static image but get accessed by the software renderer. + uint8_t CacheLock = 0; FTexture (const char *name = NULL); - }; - class FTileTexture : public FTexture { public: @@ -285,7 +298,6 @@ public: void SetName(const char* name) { Name = name; } FBitmap GetBgraBitmap(PalEntry* remap, int* ptrans) override; void Create8BitPixels(uint8_t* buffer) override; - virtual bool Resize(int w, int h) { return false; } // Regular tiles cannot be resized. }; //========================================================================== @@ -312,6 +324,51 @@ public: } }; +//========================================================================== +// +// A tile with its own pixel buffer +// +//========================================================================== + +class FLooseTile : public FTileTexture +{ + TArray RawPixels; +public: + FLooseTile(TArray &store, int width, int height) + { + useType = Art; // Whatever this was before - now it's a tile! + RawPixels = std::move(store); + SetSize(width, height); + } + + const uint8_t* Get8BitPixels() override + { + return RawPixels.Data(); + } +}; + +//========================================================================== +// +// A tile with its own pixel buffer +// +//========================================================================== + +class FDummyTile : public FTileTexture +{ + uint8_t pixel = 0; +public: + FDummyTile(int width, int height) + { + useType = Art; + SetSize(width, height); + } + + const uint8_t* Get8BitPixels() override + { + return &pixel; // do not return null. + } +}; + //========================================================================== // // A tile with a writable surface @@ -323,13 +380,10 @@ class FWritableTile : public FTileTexture protected: TArray buffer; - FWritableTile() {} public: - FWritableTile(int width, int height) + FWritableTile() { useType = Writable; - SetSize(width, height); - buffer.Resize(width * height); } const uint8_t* Get8BitPixels() override @@ -337,16 +391,24 @@ public: return buffer.Data(); } - bool Resize(int w, int h) override + uint8_t* GetWritableBuffer() override { - if (w * h == 0) + // Todo: Invalidate all hardware textures depending on this. + return buffer.Data(); + } + + bool Resize(int w, int h) + { + if (w <= 0 || h <= 0) { buffer.Reset(); + return false; } else { SetSize(w, h); buffer.Resize(w * h); + return true; } } @@ -368,6 +430,7 @@ public: useType = Restorable; Base = base; CopySize(base); + Resize(GetWidth(), GetHeight()); Reload(); } @@ -375,46 +438,6 @@ public: { Base->Create8BitPixels(buffer.Data()); } - - bool Resize(int w, int h) override - { - return false; - } -}; - -//========================================================================== -// -// A tile with a user provided buffer -// -//========================================================================== - -class FUserTile : public FTileTexture -{ - const uint8_t* RawPixels; -public: - FUserTile(const uint8_t* data, int width, int height) - : RawPixels(data) - { - useType = User; - SetSize(width, height); - } - - const uint8_t* Get8BitPixels() override - { - return RawPixels; - } -}; - -//========================================================================== -// -// A tile with a user provided buffer -// -//========================================================================== - -struct BuildTileDescriptor -{ - int tilenum; - FTexture* Texture; }; //========================================================================== @@ -427,16 +450,6 @@ struct BuildArtFile { FString filename; TArray RawData; - TArray Textures; - - void AddTiles(); - ~BuildArtFile() - { - for (auto& desc : Textures) - { - delete desc.Texture; - } - } BuildArtFile() = default; BuildArtFile(const BuildArtFile&) = delete; @@ -445,14 +458,12 @@ struct BuildArtFile { filename = std::move(other.filename); RawData = std::move(other.RawData); - Textures = std::move(other.Textures); } BuildArtFile& operator=(const BuildArtFile&& other) { filename = std::move(other.filename); RawData = std::move(other.RawData); - Textures = std::move(other.Textures); } }; @@ -464,20 +475,42 @@ struct BuildArtFile struct BuildFiles { - TArray ArtFiles; - TArray PerMapArtFiles; - void AddFile(BuildArtFile& bfd, bool permap) + FTexture* Placeholder; + TDeletingArray ArtFiles; + TDeletingArray PerMapArtFiles; + TDeletingArray AllTiles; // This is for deleting tiles when shutting down. + TDeletingArray AllMapTiles; // Same for map tiles; + FTexture* tiles[MAXTILES]; + FTexture* tilesbak[MAXTILES]; + + BuildFiles() { - if (!permap) ArtFiles.Push(std::move(bfd)); - else PerMapArtFiles.Push(std::move(bfd)); + Placeholder = new FDummyTile(0, 0); + for (auto& tile : tiles) tile = Placeholder; + for (auto& tile : tilesbak) tile = Placeholder; + } + ~BuildFiles() + { + delete Placeholder; + } + + void AddTile(int tilenum, FTexture* tex, bool permap = false); + + void AddTiles(int firsttile, TArray& store, bool permap); + + void AddFile(BuildArtFile *bfd, bool permap) + { + if (!permap) ArtFiles.Push(bfd); + else PerMapArtFiles.Push(bfd); } int FindFile(const FString& filename) { - return ArtFiles.FindEx([filename](const BuildArtFile& element) { return filename.CompareNoCase(element.filename) == 0; }); + return ArtFiles.FindEx([filename](const BuildArtFile* element) { return filename.CompareNoCase(element->filename) == 0; }); } - void LoadArtFile(const char* file, bool mapart); + void LoadArtFile(const char* file, bool mapart = false, int firsttile = -1); void CloseAllMapArt(); void LoadArtSet(const char* filename); + FTexture* ValidateCustomTile(int tilenum, int type); };