From bb67a1ba381c6f3f71ade8525e31c836007137b1 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 12 Oct 2019 08:54:06 +0200 Subject: [PATCH] - some reworking of the texture code to make integration of Build tiles easier. --- source/CMakeLists.txt | 1 + source/common/textures/buildtiles.cpp | 325 ++++++++++++++++++++++++ source/common/textures/image.cpp | 48 ---- source/common/textures/image.h | 7 +- source/common/textures/imagetexture.cpp | 11 +- source/common/textures/texture.cpp | 55 +++- source/common/textures/textures.h | 35 ++- source/duke3d/src/game.cpp | 18 +- source/rr/src/game.cpp | 23 +- 9 files changed, 403 insertions(+), 120 deletions(-) create mode 100644 source/common/textures/buildtiles.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index edf8a21b9..bdb36a558 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1052,6 +1052,7 @@ set (PCH_SOURCES common/utility/memarena.cpp common/textures/bitmap.cpp + common/textures/buildtiles.cpp common/textures/texture.cpp common/textures/image.cpp common/textures/imagetexture.cpp diff --git a/source/common/textures/buildtiles.cpp b/source/common/textures/buildtiles.cpp new file mode 100644 index 000000000..f37af634f --- /dev/null +++ b/source/common/textures/buildtiles.cpp @@ -0,0 +1,325 @@ +/* +** buildtexture.cpp +** Handling Build textures +** +**--------------------------------------------------------------------------- +** Copyright 2019 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include "files.h" +#include "zstring.h" +#include "textures.h" +#include "image.h" +#include "cache1d.h" + +struct BuildTileDescriptor +{ + int tilenum; + FTexture *Texture; +}; + +struct BuildFileDescriptor +{ + FString filename; + TArray RawData; + TArray Textures; + bool MapArt; + bool Active; + + void AddTiles(); +}; + +struct BuildFiles +{ + TArray FileDescriptors; + void AddFile(BuildFileDescriptor &bfd) + { + FileDescriptors.Push(std::move(bfd)); + } + BuildFileDescriptor *FindFile(const FString &filename) + { + auto ndx = FileDescriptors.FindEx([filename](const BuildFileDescriptor & element) { return filename.CompareNoCase(element.filename) == 0; }); + if (ndx < FileDescriptors.Size()) return &FileDescriptors[ndx]; + return nullptr; + } + void LoadArtFile(const char *file, bool mapart); + void CloseAllMapArt(); + int LoadIndexedFile(const char* base, int index); + void LoadArtSet(const char* filename); + +}; + +static BuildFiles alltilefiles; + +//=========================================================================== +// +// AddTiles +// +// Adds all the tiles in an artfile to the texture manager. +// +//=========================================================================== + +void BuildFileDescriptor::AddTiles () +{ + + 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]); + 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]; + + for (int i = tilestart; i <= tileend; ++i) + { + int pic = i - tilestart; + int width = LittleShort(tilesizx[pic]); + int height = LittleShort(tilesizy[pic]); + uint32_t anm = LittleLong(picanm[pic]); + int xoffs = (int8_t)((anm >> 8) & 255) + width/2; + int yoffs = (int8_t)((anm >> 16) & 255) + height/2; + int size = width*height; + + 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 = FTexture::GetTileTexture(name, RawData, uint32_t(tiledata - tiles), width, height, xoffs, yoffs); + BuildTileDescriptor desc; + //Textures.Push(); + tiledata += size; + } +} + +//=========================================================================== +// +// CountTiles +// +// Returns the number of tiles provided by an artfile +// +//=========================================================================== + +int CountTiles (const void *RawData) +{ + int version = LittleLong(*(uint32_t *)RawData); + if (version != 1) + { + return 0; + } + + int tilestart = LittleLong(((uint32_t *)RawData)[2]); + int tileend = LittleLong(((uint32_t *)RawData)[3]); + + return tileend >= tilestart ? tileend - tilestart + 1 : 0; +} + +//=========================================================================== +// +// CloseAllMapArt +// +// Closes all per-map ART files +// +//=========================================================================== + +void BuildFiles::CloseAllMapArt() +{ + for (auto& fd : FileDescriptors) + { + if (fd.MapArt) + { + fd.Active = false; + fd.RawData.Reset(); + } + } +} + +//=========================================================================== +// +// LoadArtFile +// +// Returns the number of tiles found. Also loads all the data for +// R_InitBuildTiles() to process later. +// +// let's load everything into memory on startup. +// Even for Ion Fury this will merely add 60 MB, because the engine already needs to cache the data, albeit in a compressed-per-lump form, +// so its 100MB art file will only have a partial impact on memory. +// +//=========================================================================== + +void BuildFiles::LoadArtFile(const char *fn, bool mapart) +{ + auto old = FindFile(fn); + if (old) + { + FileReader fr = kopenFileReader(fn, 0); + if (fr.isOpen()) + { + auto artdata = fr.Read(); + const uint8_t *artptr = artdata.Data(); + if (artdata.Size() > 16) + { + if (memcmp(artptr, "BUILDART", 8) == 0) artptr += 8; + // Only load the data if the header is present + if (CountTiles(artptr) > 0) + { + FileDescriptors.Reserve(1); + auto &fd = FileDescriptors.Last(); + fd.filename = fn; + fd.MapArt = mapart; + fd.Active = true; + fd.RawData = std::move(artdata); + fd.AddTiles(); + } + } + } + } +} + +#if 0 + +//=========================================================================== +// +// Returns: +// 0: successfully read ART file +// >0: error with the ART file +// -1: ART file does not exist +//<-1: per-map ART issue +// +//=========================================================================== + +int BuildFiles::LoadIndexedFile(const char *base, int index) +{ + FStringf name(base, index); + + const char* fn = artGetIndexedFileName(tilefilei); + const int32_t permap = (tilefilei >= MAXARTFILES_BASE); // is it a per-map ART file? + buildvfs_kfd fil; + + if ((fil = kopen4loadfrommod(fn, 0)) != buildvfs_kfd_invalid) + { + artheader_t local; + int const headerval = artReadHeader(fil, fn, &local); + if (headerval != 0) + { + kclose(fil); + return headerval; + } + + if (permap) + { + // Check whether we can evict existing tiles to make place for + // per-map ART ones. + for (int i = local.tilestart; i <= local.tileend; i++) + { + // Tiles having dummytile replacements or those that are + // cache1d-locked can't be replaced. + if (faketile[i >> 3] & pow2char[i & 7] || walock[i] >= 200) + { + initprintf("loadpics: per-map ART file \"%s\": " + "tile %d has dummytile or is locked\n", fn, i); + kclose(fil); + return -3; + } + } + + // Free existing tiles from the cache1d. CACHE1D_FREE + Bmemset(&tileptr[local.tilestart], 0, local.numtiles * sizeof(uint8_t*)); + Bmemset(&tiledata[local.tilestart], 0, local.numtiles * sizeof(uint8_t*)); + Bmemset(&walock[local.tilestart], 1, local.numtiles * sizeof(walock[0])); + } + + artReadManifest(fil, &local); + + if (cache1d_file_fromzip(fil)) + { + if (permap) + artPreloadFileSafe(fil, &local); + else + artPreloadFile(fil, &local); + } + else + { + int offscount = ktell(fil); + + for (bssize_t i = local.tilestart; i <= local.tileend; ++i) + { + int const dasiz = tilesiz[i].x * tilesiz[i].y; + + tilefilenum[i] = tilefilei; + tilefileoffs[i] = offscount; + + offscount += dasiz; + // artsize += ((dasiz+15)&0xfffffff0); + } + } + + kclose(fil); + return 0; + } + + return -1; +} + +// +// loadpics +// +int32_t BuildFiles::LoadArtSet(const char* filename) +{ + Bstrncpyz(artfilenameformat, filename, sizeof(artfilenameformat)); + + Bmemset(&tilesizearray[0], 0, sizeof(vec2_16_t) * MAXTILES); + Bmemset(picanm, 0, sizeof(picanm)); + + for (auto& rot : rottile) + rot = { -1, -1 }; + + // artsize = 0; + + for (int tilefilei = 0; tilefilei < MAXARTFILES_BASE; tilefilei++) + artReadIndexedFile(tilefilei); + + Bmemset(gotpic, 0, sizeof(gotpic)); + + //cachesize = min((int32_t)((Bgetsysmemsize()/100)*60),max(artsize,askedsize)); + cachesize = (Bgetsysmemsize() <= (uint32_t)askedsize) ? (int32_t)((Bgetsysmemsize() / 100) * 60) : askedsize; + pic = Xaligned_alloc(Bgetpagesize(), cachesize); + cacheInitBuffer((intptr_t)pic, cachesize); + + artUpdateManifest(); + + artfil = buildvfs_kfd_invalid; + artfilnum = -1; + artfilplc = 0L; + + return 0; +} + +#endif \ No newline at end of file diff --git a/source/common/textures/image.cpp b/source/common/textures/image.cpp index c899d69f9..5fd7759e7 100644 --- a/source/common/textures/image.cpp +++ b/source/common/textures/image.cpp @@ -43,54 +43,6 @@ int FImageSource::NextID; -//=========================================================================== -// -// the default just returns an empty texture. -// -//=========================================================================== - -void FImageSource::CreatePalettedPixels(uint8_t *buffer) -{ - memset(buffer, 0, Width * Height); -} - -const uint8_t* FImageSource::GetPalettedPixels() -{ - return nullptr; -} - -//=========================================================================== -// -// FImageSource::CopyPixels -// -// this is the generic case that can handle -// any properly implemented texture for software rendering. -// Its drawback is that it is limited to the base palette which is -// why all classes that handle different palettes should subclass this -// method -// -//=========================================================================== - -int FImageSource::CopyTranslatedPixels(FBitmap *bmp, PalEntry *remap) -{ - TArray buffer; - const uint8_t* ppix = GetPalettedPixels(); - if (ppix == nullptr) - { - buffer.Resize(Width * Height); - CreatePalettedPixels(buffer.Data()); - ppix = buffer.Data(); - } - bmp->CopyPixelData(0, 0, ppix, Width, Height, Height, 1, 0, remap); - return 0; -} - -int FImageSource::CopyPixels(FBitmap* bmp, int conversion) -{ - return CopyTranslatedPixels(bmp, ImageHelpers::BaseColors); // This should never get called for ART tiles. -} - - //========================================================================== // // diff --git a/source/common/textures/image.h b/source/common/textures/image.h index d79af8e4e..3659bc4be 100644 --- a/source/common/textures/image.h +++ b/source/common/textures/image.h @@ -61,10 +61,8 @@ public: // 'noremap0' will only be looked at by FPatchTexture and forwarded by FMultipatchTexture. static FImageSource * GetImage(const char *name); - virtual void CreatePalettedPixels(uint8_t *destbuffer); - virtual const uint8_t* GetPalettedPixels(); - virtual int CopyPixels(FBitmap* bmp, int conversion); // This will always ignore 'luminance'. - int CopyTranslatedPixels(FBitmap* bmp, PalEntry* remap); + virtual void CreatePalettedPixels(uint8_t *destbuffer) = 0; + virtual int CopyPixels(FBitmap* bmp, int conversion) = 0; // This will always ignore 'luminance'. // Conversion option @@ -126,7 +124,6 @@ class FImageTexture : public FTexture public: FImageTexture (FImageSource *image, const char *name = nullptr); void Create8BitPixels(uint8_t* buffer) override; - const uint8_t* Get8BitPixels() override; void SetImage(FImageSource *img) // This is only for the multipatch texture builder! { diff --git a/source/common/textures/imagetexture.cpp b/source/common/textures/imagetexture.cpp index f647379f4..5d27d58e1 100644 --- a/source/common/textures/imagetexture.cpp +++ b/source/common/textures/imagetexture.cpp @@ -51,8 +51,8 @@ FImageTexture::FImageTexture(FImageSource *img, const char *name) mImage = img; if (img != nullptr) { - Width = img->GetWidth(); - Height = img->GetHeight(); + Size.x = img->GetWidth(); + Size.y = img->GetHeight(); auto offsets = img->GetOffsets(); LeftOffset = offsets.first; @@ -72,7 +72,7 @@ FImageTexture::FImageTexture(FImageSource *img, const char *name) FBitmap FImageTexture::GetBgraBitmap(PalEntry *p, int *trans) { FBitmap bmp; - bmp.Create(Width, Height); + bmp.Create(Size.x, Size.y); mImage->CopyPixels(&bmp, 0); // Todo: Handle translations. return bmp; } @@ -83,11 +83,6 @@ FBitmap FImageTexture::GetBgraBitmap(PalEntry *p, int *trans) // //=========================================================================== -const uint8_t *FImageTexture::Get8BitPixels() -{ - return mImage->GetPalettedPixels(); -} - void FImageTexture::Create8BitPixels(uint8_t* buffer) { return mImage->CreatePalettedPixels(buffer); diff --git a/source/common/textures/texture.cpp b/source/common/textures/texture.cpp index 806b9f3a9..52c25f813 100644 --- a/source/common/textures/texture.cpp +++ b/source/common/textures/texture.cpp @@ -93,7 +93,7 @@ FTexture::~FTexture () FBitmap FTexture::GetBgraBitmap(PalEntry *remap, int *ptrans) { FBitmap bmp; - bmp.Create(Width, Height); + bmp.Create(Size.x, Size.y); return bmp; } @@ -434,35 +434,62 @@ FTexture *FTexture::GetTexture(const char *path) //========================================================================== // -// A minimalistic wrapper around a Build ART file. -// The data in here is already the format we need. +// Base class for Build tile textures +// This needs a few subclasses for different use cases. // //========================================================================== -class FBuildTexture : public FImageSource +FBitmap FTileTexture::GetBgraBitmap(PalEntry* remap, int* ptrans) { - const uint8_t* RawPixels; + FBitmap bmp; + bmp.Create(Size.x, Size.y); + const uint8_t* ppix = Get8BitPixels(); // any properly implemented tile MUST return something valid here. + if (ppix) bmp.CopyPixelData(0, 0, ppix, Size.x, Size.y, Size.y, 1, 0, remap); + return bmp; +} + +void FTileTexture::Create8BitPixels(uint8_t* buffer) +{ + auto pix = Get8BitPixels(); + if (pix) memcpy(buffer, pix, Size.x * Size.y); +} + + +//========================================================================== +// +// A tile coming from an ART file. +// +//========================================================================== + +class FArtTile : public FTileTexture +{ + const TArray &RawPixels; + const uint32_t Offset; public: - FBuildTexture(const uint8_t* raw, int width, int height, int left, int top) - : RawPixels(raw) + FArtTile(const TArray& backingstore, uint32_t offset, int width, int height, int left, int top) + : RawPixels(backingstore), Offset(offset) { - Width = width; - Height = height; + Size.x = (int16_t)width; + Size.y = (int16_t)height; LeftOffset = left; TopOffset = top; } - const uint8_t* GetPalettedPixels() override + const uint8_t* Get8BitPixels() override { - return RawPixels; + return RawPixels.Data() ? RawPixels.Data() + Offset : nullptr; } }; -FTexture* FTexture::GetTileTexture(const char* name, const uint8_t* data, int width, int height, int xofs, int yofs) +FTexture* FTexture::GetTileTexture(const char* name, const TArray& backingstore, uint32_t offset, int width, int height, int xofs, int yofs) { auto res = textures.CheckKey(name); if (res) return *res; - auto tex = new FImageTexture(new FBuildTexture(data, width, height, xofs, yofs), name); - if (tex) textures.Insert(name, tex); + auto tex = new FArtTile(backingstore, offset, width, height, xofs, yofs); + if (tex) + { + tex->SetName(name); + textures.Insert(name, tex); + } return tex; } \ No newline at end of file diff --git a/source/common/textures/textures.h b/source/common/textures/textures.h index fdf033c67..9fe0d7f93 100644 --- a/source/common/textures/textures.h +++ b/source/common/textures/textures.h @@ -58,6 +58,10 @@ 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. +{ + int16_t x, y; +}; class FBitmap; @@ -147,7 +151,8 @@ class FTexture public: static FTexture *CreateTexture(const char *name); static FTexture* GetTexture(const char* path); - static FTexture* GetTileTexture(const char* name, const uint8_t* data, int width, int height, int xofs, int yofs); + static FTexture* GetTileTexture(const char* name, const TArray& backingstore, uint32_t offset, int width, int height, int xofs, int yofs); + virtual ~FTexture (); virtual FImageSource *GetImage() const { return nullptr; } @@ -160,13 +165,15 @@ public: // Returns the whole texture, stored in column-major order virtual void Create8BitPixels(uint8_t* buffer); virtual const uint8_t* Get8BitPixels(); + virtual uint8_t* GetWritableBuffer() { return nullptr; } // For dynamic tiles. Requesting this must also invalidate the texture. virtual FBitmap GetBgraBitmap(PalEntry *remap, int *trans = nullptr); static int SmoothEdges(unsigned char * buffer,int w, int h); static PalEntry averageColor(const uint32_t *data, int size, int maxout); - int GetWidth() { return Width; } - int GetHeight() { return Height; } + int GetWidth() { return Size.x; } + int GetHeight() { return Size.y; } + const size_16_t &GetSize() { return Size; } int GetLeftOffset() { return LeftOffset; } int GetTopOffset() { return TopOffset; } FTextureBuffer CreateTexBuffer(int translation, int flags = 0); @@ -178,15 +185,20 @@ protected: void CopySize(FTexture *BaseTexture) { - Width = BaseTexture->GetWidth(); - Height = BaseTexture->GetHeight(); + Size.x = BaseTexture->GetWidth(); + Size.y = BaseTexture->GetHeight(); TopOffset = BaseTexture->TopOffset; TopOffset = BaseTexture->TopOffset; } - int Width = 0, Height = 0; - int LeftOffset = 0, TopOffset = 0; + void SetSize(int w, int h) + { + Size = { int16_t(w), int16_t(h) }; + } + FString Name; + size_16_t Size = { 0,0 }; // Keep this in the native format so that we can use it without copying it around. + int LeftOffset = 0, TopOffset = 0; uint8_t bMasked = true; // Texture (might) have holes int8_t bTranslucent = -1; // Does this texture have an active alpha channel? bool skyColorDone = false; @@ -197,6 +209,15 @@ protected: }; + +class FTileTexture : public FTexture +{ +public: + void SetName(const char* name) { Name = name; } + FBitmap GetBgraBitmap(PalEntry* remap, int* ptrans) override; + void Create8BitPixels(uint8_t* buffer) override; +}; + #endif diff --git a/source/duke3d/src/game.cpp b/source/duke3d/src/game.cpp index 4daf52f7f..0b6530f05 100644 --- a/source/duke3d/src/game.cpp +++ b/source/duke3d/src/game.cpp @@ -6129,23 +6129,7 @@ static void G_Startup(void) if (numplayers > 1) initprintf("Multiplayer initialized.\n"); - char *cwd; - - if (g_modDir[0] != '/' && (cwd = buildvfs_getcwd(NULL, 0))) - { - buildvfs_chdir(g_modDir); - if (artLoadFiles("tiles%03i.art", MAXCACHE1DSIZE) < 0) - { - buildvfs_chdir(cwd); - if (artLoadFiles("tiles%03i.art", MAXCACHE1DSIZE) < 0) - G_GameExit("Failed loading art."); - } - buildvfs_chdir(cwd); -#ifndef __ANDROID__ //This crashes on *some* Android devices. Small onetime memory leak. TODO fix above function - Xfree(cwd); -#endif - } - else if (artLoadFiles("tiles%03i.art",MAXCACHE1DSIZE) < 0) + if (artLoadFiles("tiles%03i.art",MAXCACHE1DSIZE) < 0) G_GameExit("Failed loading art."); cacheAllSounds(); diff --git a/source/rr/src/game.cpp b/source/rr/src/game.cpp index 466fe71c3..4bcd5a958 100644 --- a/source/rr/src/game.cpp +++ b/source/rr/src/game.cpp @@ -7532,27 +7532,8 @@ static void G_Startup(void) if (numplayers > 1) initprintf("Multiplayer initialized.\n"); - { - char *cwd; - - if (g_modDir[0] != '/' && (cwd = getcwd(NULL, 0))) - { - Bchdir(g_modDir); - if (artLoadFiles("tiles%03i.art",MAXCACHE1DSIZE) < 0) - { - Bchdir(cwd); - if (artLoadFiles("tiles%03i.art",MAXCACHE1DSIZE) < 0) - G_GameExit("Failed loading art."); - } - Bchdir(cwd); -#ifndef __ANDROID__ //This crashes on *some* Android devices. Small onetime memory leak. TODO fix above function - Bfree(cwd); -#endif - - } - else if (artLoadFiles("tiles%03i.art",MAXCACHE1DSIZE) < 0) - G_GameExit("Failed loading art."); - } + if (artLoadFiles("tiles%03i.art",MAXCACHE1DSIZE) < 0) + G_GameExit("Failed loading art."); // Make the fullscreen nuke logo background non-fullbright. Has to be // after dynamic tile remapping (from C_Compile) and loading tiles.