Index: ps/trunk/source/lib/bits.cpp =================================================================== --- ps/trunk/source/lib/bits.cpp (revision 24351) +++ ps/trunk/source/lib/bits.cpp (revision 24352) @@ -1,42 +1,44 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * bit-twiddling. */ #include "precompiled.h" #include "lib/bits.h" +#include + static inline u32 get_float_bits(const float x) { u32 ret; memcpy(&ret, &x, 4); return ret; } int floor_log2(const float x) { const u32 i = get_float_bits(x); const u32 biased_exp = (i >> 23) & 0xFF; return (int)biased_exp - 127; } Index: ps/trunk/source/gui/ObjectTypes/CHotkeyPicker.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CHotkeyPicker.h (revision 24351) +++ ps/trunk/source/gui/ObjectTypes/CHotkeyPicker.h (revision 24352) @@ -1,79 +1,80 @@ /* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CHOTKEYPICKER #define INCLUDED_CHOTKEYPICKER #include "gui/CGUI.h" +#include "gui/ObjectBases/IGUIObject.h" #include "lib/external_libraries/libsdl.h" #include "ps/CStr.h" #include class ScriptInterface; /** * When in focus, returns all currently pressed keys. * After a set time without changes, it will trigger a "combination" event. * * Used to create new hotkey combinations in-game. Mostly custom. * This object does not draw anything. */ class CHotkeyPicker : public IGUIObject { GUI_OBJECT(CHotkeyPicker) friend class ScriptInterface; public: CHotkeyPicker(CGUI& pGUI); virtual ~CHotkeyPicker(); // Do nothing. virtual void Draw() {}; // Checks if the timer has passed and we need to fire a "combination" event. virtual void Tick(); // React to blur/focus. virtual void HandleMessage(SGUIMessage& Message); // Pre-empt events: this is our sole purpose. virtual InReaction PreemptEvent(const SDL_Event_* ev); protected: // Fire an event with m_KeysPressed as argument. void FireEvent(const CStr& event); // Time without changes until a "combination" event is sent. float m_TimeToCombination; // Time of the last registered key change. double m_LastKeyChange; // Keep track of which keys we are pressing, and precompute their name for JS code. struct Key { // The scancode is used for fast comparisons. SDL_Scancode code; // This is the name ultimately stored in the config file. CStr scancodeName; }; std::vector m_KeysPressed; static const CStr EventNameCombination; static const CStr EventNameKeyChange; }; #endif // INCLUDED_CHOTKEYPICKER Index: ps/trunk/source/lib/file/archive/codec_zlib.cpp =================================================================== --- ps/trunk/source/lib/file/archive/codec_zlib.cpp (revision 24351) +++ ps/trunk/source/lib/file/archive/codec_zlib.cpp (revision 24352) @@ -1,306 +1,307 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "precompiled.h" #include "lib/file/archive/codec_zlib.h" #include "lib/alignment.h" #include "lib/file/archive/codec.h" #include "lib/external_libraries/zlib.h" #include "lib/sysdep/cpu.h" +#include class Codec_ZLib : public ICodec { public: u32 UpdateChecksum(u32 checksum, const u8* in, size_t inSize) const { #if CODEC_COMPUTE_CHECKSUM return (u32)crc32(checksum, in, (uInt)inSize); #else UNUSED2(checksum); UNUSED2(in); UNUSED2(inSize); return 0; #endif } protected: u32 InitializeChecksum() { #if CODEC_COMPUTE_CHECKSUM return crc32(0, 0, 0); #else return 0; #endif } }; //----------------------------------------------------------------------------- class Codec_ZLibNone : public Codec_ZLib { public: Codec_ZLibNone() { Reset(); } virtual ~Codec_ZLibNone() { } virtual size_t MaxOutputSize(size_t inSize) const { return inSize; } virtual Status Reset() { m_checksum = InitializeChecksum(); return INFO::OK; } virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced) { const size_t transferSize = std::min(inSize, outSize); memcpy(out, in, transferSize); inConsumed = outProduced = transferSize; m_checksum = UpdateChecksum(m_checksum, out, outProduced); return INFO::OK; } virtual Status Finish(u32& checksum, size_t& outProduced) { outProduced = 0; checksum = m_checksum; return INFO::OK; } private: u32 m_checksum; }; //----------------------------------------------------------------------------- class CodecZLibStream : public Codec_ZLib { protected: CodecZLibStream() { memset(&m_zs, 0, sizeof(m_zs)); m_checksum = InitializeChecksum(); } static Status LibError_from_zlib(int zlib_ret) { switch(zlib_ret) { case Z_OK: return INFO::OK; case Z_STREAM_END: WARN_RETURN(ERR::FAIL); case Z_MEM_ERROR: WARN_RETURN(ERR::NO_MEM); case Z_DATA_ERROR: WARN_RETURN(ERR::CORRUPTED); case Z_STREAM_ERROR: WARN_RETURN(ERR::INVALID_PARAM); default: WARN_RETURN(ERR::FAIL); } } static void WarnIfZLibError(int zlib_ret) { (void)LibError_from_zlib(zlib_ret); } typedef int ZEXPORT (*ZLibFunc)(z_streamp strm, int flush); Status CallStreamFunc(ZLibFunc func, int flush, const u8* in, const size_t inSize, u8* out, const size_t outSize, size_t& inConsumed, size_t& outProduced) { m_zs.next_in = (Byte*)in; m_zs.avail_in = (uInt)inSize; m_zs.next_out = (Byte*)out; m_zs.avail_out = (uInt)outSize; int ret = func(&m_zs, flush); // sanity check: if ZLib reports end of stream, all input data // must have been consumed. if(ret == Z_STREAM_END) { ENSURE(m_zs.avail_in == 0); ret = Z_OK; } ENSURE(inSize >= m_zs.avail_in && outSize >= m_zs.avail_out); inConsumed = inSize - m_zs.avail_in; outProduced = outSize - m_zs.avail_out; return LibError_from_zlib(ret); } mutable z_stream m_zs; // note: z_stream does contain an 'adler' checksum field, but that's // not updated in streams lacking a gzip header, so we'll have to // calculate a checksum ourselves. // adler32 is somewhat weaker than CRC32, but a more important argument // is that we should use the latter for compatibility with Zip archives. mutable u32 m_checksum; }; //----------------------------------------------------------------------------- class Compressor_ZLib : public CodecZLibStream { public: Compressor_ZLib() { // note: with Z_BEST_COMPRESSION, 78% percent of // archive builder CPU time is spent in ZLib, even though // that is interleaved with IO; everything else is negligible. // we prefer faster speed at the cost of 1.5% larger archives. const int level = Z_BEST_SPEED; const int windowBits = -MAX_WBITS; // max window size; omit ZLib header const int memLevel = 9; // max speed; total mem ~= 384KiB const int strategy = Z_DEFAULT_STRATEGY; // normal data - not RLE const int ret = deflateInit2(&m_zs, level, Z_DEFLATED, windowBits, memLevel, strategy); ENSURE(ret == Z_OK); } virtual ~Compressor_ZLib() { const int ret = deflateEnd(&m_zs); WarnIfZLibError(ret); } virtual size_t MaxOutputSize(size_t inSize) const { return (size_t)deflateBound(&m_zs, (uLong)inSize); } virtual Status Reset() { m_checksum = InitializeChecksum(); const int ret = deflateReset(&m_zs); return LibError_from_zlib(ret); } virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced) { m_checksum = UpdateChecksum(m_checksum, in, inSize); return CodecZLibStream::CallStreamFunc(deflate, 0, in, inSize, out, outSize, inConsumed, outProduced); } virtual Status Finish(u32& checksum, size_t& outProduced) { const uInt availOut = m_zs.avail_out; // notify zlib that no more data is forthcoming and have it flush output. // our output buffer has enough space due to use of deflateBound; // therefore, deflate must return Z_STREAM_END. const int ret = deflate(&m_zs, Z_FINISH); ENSURE(ret == Z_STREAM_END); outProduced = size_t(availOut - m_zs.avail_out); checksum = m_checksum; return INFO::OK; } }; //----------------------------------------------------------------------------- class Decompressor_ZLib : public CodecZLibStream { public: Decompressor_ZLib() { const int windowBits = -MAX_WBITS; // max window size; omit ZLib header const int ret = inflateInit2(&m_zs, windowBits); ENSURE(ret == Z_OK); } virtual ~Decompressor_ZLib() { const int ret = inflateEnd(&m_zs); WarnIfZLibError(ret); } virtual size_t MaxOutputSize(size_t inSize) const { // relying on an upper bound for the output is a really bad idea for // large files. archive formats store the uncompressed file sizes, // so callers should use that when allocating the output buffer. ENSURE(inSize < 1*MiB); return inSize*1032; // see http://www.zlib.org/zlib_tech.html } virtual Status Reset() { m_checksum = InitializeChecksum(); const int ret = inflateReset(&m_zs); return LibError_from_zlib(ret); } virtual Status Process(const u8* in, size_t inSize, u8* out, size_t outSize, size_t& inConsumed, size_t& outProduced) { const Status ret = CodecZLibStream::CallStreamFunc(inflate, Z_SYNC_FLUSH, in, inSize, out, outSize, inConsumed, outProduced); m_checksum = UpdateChecksum(m_checksum, out, outProduced); return ret; } virtual Status Finish(u32& checksum, size_t& outProduced) { // no action needed - decompression always flushes immediately. outProduced = 0; checksum = m_checksum; return INFO::OK; } }; //----------------------------------------------------------------------------- PICodec CreateCodec_ZLibNone() { return PICodec(new Codec_ZLibNone); } PICodec CreateCompressor_ZLibDeflate() { return PICodec(new Compressor_ZLib); } PICodec CreateDecompressor_ZLibDeflate() { return PICodec (new Decompressor_ZLib); } Index: ps/trunk/source/lib/file/vfs/vfs_path.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_path.h (revision 24351) +++ ps/trunk/source/lib/file/vfs/vfs_path.h (revision 24352) @@ -1,44 +1,46 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INCLUDED_VFS_PATH #define INCLUDED_VFS_PATH #include "lib/path.h" +#include + /** * VFS path of the form "(dir/)*file?" * * in other words: the root directory is "" and paths are separated by '/'. * a trailing slash is allowed for directory names. * rationale: it is important to avoid a leading slash because that might be * confused with an absolute POSIX path. * * there is no restriction on path length; when dimensioning character * arrays, prefer PATH_MAX. **/ typedef Path VfsPath; typedef std::vector VfsPaths; #endif // #ifndef INCLUDED_VFS_PATH Index: ps/trunk/source/gui/ObjectTypes/CList.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CList.h (revision 24351) +++ ps/trunk/source/gui/ObjectTypes/CList.h (revision 24352) @@ -1,162 +1,163 @@ /* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CLIST #define INCLUDED_CLIST #include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIObject.h" #include "gui/ObjectBases/IGUIScrollBarOwner.h" #include "gui/ObjectBases/IGUITextOwner.h" #include "gui/SettingTypes/CGUIList.h" #include /** * Create a list of elements, where one can be selected * by the user. The control will use a pre-processed * text-object for each element, which will be managed * by the IGUITextOwner structure. * * A scroll-bar will appear when needed. This will be * achieved with the IGUIScrollBarOwner structure. */ class CList : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner { GUI_OBJECT(CList) friend JSI_GUIProxy; public: CList(CGUI& pGUI); virtual ~CList(); /** * @see IGUIObject#ResetStates() */ virtual void ResetStates(); /** * @see IGUIObject#UpdateCachedSize() */ virtual void UpdateCachedSize(); /** * Adds an item last to the list. */ virtual void AddItem(const CGUIString& str, const CGUIString& data); protected: /** * Sets up text, should be called every time changes has been * made that can change the visual. * @param append - if true, we assume we only need to render the new element at the end of the list. */ virtual void SetupText(); virtual void SetupText(bool append); /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * Handle events manually to catch keyboard inputting. */ virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev); /** * Draws the List box */ virtual void Draw(); virtual void CreateJSObject(); /** * Easy select elements functions */ virtual void SelectNextElement(); virtual void SelectPrevElement(); virtual void SelectFirstElement(); virtual void SelectLastElement(); /** * Handle the \ tag. */ virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); // Called every time the auto-scrolling should be checked. void UpdateAutoScroll(); // Extended drawing interface, this is so that classes built on the this one // can use other sprite names. virtual void DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor); // Get the area of the list. This is so that it can easily be changed, like in CDropDown // where the area is not equal to m_CachedActualSize. virtual CRect GetListRect() const { return m_CachedActualSize; } // Returns whether SetupText() has run since the last message was received // (and thus whether list items have possibly changed). virtual bool GetModified() const { return m_Modified; } /** * List of each element's relative y position. Will be * one larger than m_Items, because it will end with the * bottom of the last element. First element will always * be zero, but still stored for easy handling. */ std::vector m_ItemsYPositions; virtual int GetHoveredItem(); // Settings float m_BufferZone; CStrW m_Font; bool m_ScrollBar; CStr m_ScrollBarStyle; bool m_ScrollBottom; CStrW m_SoundDisabled; CStrW m_SoundSelected; CGUISpriteInstance m_Sprite; CGUISpriteInstance m_SpriteSelectArea; i32 m_CellID; EAlign m_TextAlign; CGUIColor m_TextColor; CGUIColor m_TextColorSelected; i32 m_Selected; bool m_AutoScroll; i32 m_Hovered; CGUIList m_List; CGUIList m_ListData; private: static const CStr EventNameSelectionChange; static const CStr EventNameHoverChange; static const CStr EventNameMouseLeftClickItem; static const CStr EventNameMouseLeftDoubleClickItem; // Whether the list's items have been modified since last handling a message. bool m_Modified; // Used for doubleclick registration int m_PrevSelectedItem; // Last time a click on an item was issued double m_LastItemClickTime; }; #endif // INCLUDED_CLIST Index: ps/trunk/source/gui/ObjectTypes/CTooltip.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CTooltip.h (revision 24351) +++ ps/trunk/source/gui/ObjectTypes/CTooltip.h (revision 24352) @@ -1,68 +1,69 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_CTOOLTIP #define INCLUDED_CTOOLTIP -#include "gui/ObjectBases/IGUITextOwner.h" #include "gui/CGUISprite.h" +#include "gui/ObjectBases/IGUIObject.h" +#include "gui/ObjectBases/IGUITextOwner.h" #include "gui/SettingTypes/CGUIString.h" /** * Dynamic tooltips. Similar to CText. */ class CTooltip : public IGUIObject, public IGUITextOwner { GUI_OBJECT(CTooltip) public: CTooltip(CGUI& pGUI); virtual ~CTooltip(); protected: void SetupText(); /** * @see IGUIObject#UpdateCachedSize() */ void UpdateCachedSize(); /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); virtual void Draw(); // Settings float m_BufferZone; CGUIString m_Caption; CStrW m_Font; CGUISpriteInstance m_Sprite; i32 m_Delay; CGUIColor m_TextColor; float m_MaxWidth; CPos m_Offset; EVAlign m_Anchor; EAlign m_TextAlign; bool m_Independent; CPos m_MousePos; CStr m_UseObject; bool m_HideObject; }; #endif // INCLUDED_CTOOLTIP Index: ps/trunk/source/lib/allocators/allocator_policies.h =================================================================== --- ps/trunk/source/lib/allocators/allocator_policies.h (revision 24351) +++ ps/trunk/source/lib/allocators/allocator_policies.h (revision 24352) @@ -1,354 +1,355 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * policy class templates for allocators. */ #ifndef ALLOCATOR_POLICIES #define ALLOCATOR_POLICIES #include "lib/alignment.h" // pageSize #include "lib/allocators/allocator_adapters.h" #include "lib/allocators/freelist.h" +#include namespace Allocators { //----------------------------------------------------------------------------- // Growth // O(N) allocations, O(1) wasted space. template struct Growth_Linear { size_t operator()(size_t oldSize) const { return oldSize + increment; } }; // O(log r) allocations, O(N) wasted space. NB: the common choice of // expansion factor r = 2 (e.g. in the GCC STL) prevents // Storage_Reallocate from reusing previous memory blocks, // thus constantly growing the heap and decreasing locality. // Alexandrescu [C++ and Beyond 2010] recommends r < 33/25. // we approximate this with a power of two divisor to allow shifting. // C++ does allow reference-to-float template parameters, but // integer arithmetic is expected to be faster. // (Storage_Commit should use 2:1 because it is cheaper to // compute and retains power-of-two sizes.) template struct Growth_Exponential { size_t operator()(size_t oldSize) const { const size_t product = oldSize * multiplier; // detect overflow, but allow equality in case oldSize = 0, // which isn't a problem because Storage_Commit::Expand // raises it to requiredCapacity. ASSERT(product >= oldSize); return product / divisor; } }; //----------------------------------------------------------------------------- // Storage // a contiguous region of memory (not just an "array", because // allocators such as Arena append variable-sized intervals). // // we don't store smart pointers because storage usually doesn't need // to be copied, and ICC 11 sometimes wasn't able to inline Address(). struct Storage { // @return starting address (alignment depends on the allocator). uintptr_t Address() const; // @return size [bytes] of currently accessible memory. size_t Capacity() const; // @return largest possible capacity [bytes]. size_t MaxCapacity() const; // expand Capacity() to at least requiredCapacity (possibly more // depending on GrowthPolicy). // @param requiredCapacity > Capacity() // @return false and leave Capacity() unchanged if expansion failed, // which is guaranteed to happen if requiredCapacity > MaxCapacity(). bool Expand(size_t requiredCapacity); }; // allocate once and refuse subsequent expansion. template > class Storage_Fixed { NONCOPYABLE(Storage_Fixed); public: Storage_Fixed(size_t size) : maxCapacity(size) , storage(allocator.allocate(maxCapacity)) { } ~Storage_Fixed() { allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return maxCapacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t UNUSED(requiredCapacity)) { return false; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; }; // unlimited expansion by allocating larger storage and copying. // (basically equivalent to std::vector, although Growth_Exponential // is much more cache and allocator-friendly than the GCC STL) template > class Storage_Reallocate { NONCOPYABLE(Storage_Reallocate); public: Storage_Reallocate(size_t initialCapacity) : capacity(initialCapacity) , storage(allocator.allocate(initialCapacity)) { } ~Storage_Reallocate() { allocator.deallocate(storage, capacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return capacity; } size_t MaxCapacity() const { return std::numeric_limits::max(); } bool Expand(size_t requiredCapacity) { size_t newCapacity = std::max(requiredCapacity, GrowthPolicy()(capacity)); void* newStorage = allocator.allocate(newCapacity); if(!newStorage) return false; memcpy(newStorage, storage, capacity); std::swap(capacity, newCapacity); std::swap(storage, newStorage); allocator.deallocate(newStorage, newCapacity); // free PREVIOUS storage return true; } private: Allocator allocator; size_t capacity; // must be initialized before storage void* storage; }; // expand up to the limit of the allocated address space by // committing physical memory. this avoids copying and // reduces wasted physical memory. template, class GrowthPolicy = Growth_Exponential<2,1> > class Storage_Commit { NONCOPYABLE(Storage_Commit); public: Storage_Commit(size_t maxCapacity_) : maxCapacity(Align(maxCapacity_)) // see Expand , storage(allocator.allocate(maxCapacity)) , capacity(0) { } ~Storage_Commit() { allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return capacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t requiredCapacity) { size_t newCapacity = std::max(requiredCapacity, GrowthPolicy()(capacity)); // reduce the number of expensive commits by accurately // reflecting the actual capacity. this is safe because // we also round up maxCapacity. newCapacity = Align(newCapacity); if(newCapacity > maxCapacity) return false; if(!vm::Commit(Address()+capacity, newCapacity-capacity)) return false; capacity = newCapacity; return true; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; size_t capacity; }; // implicitly expand up to the limit of the allocated address space by // committing physical memory when a page is first accessed. // this is basically equivalent to Storage_Commit with Growth_Linear, // except that there is no need to call Expand. template > class Storage_AutoCommit { NONCOPYABLE(Storage_AutoCommit); public: Storage_AutoCommit(size_t maxCapacity_) : maxCapacity(Align(maxCapacity_)) // match user's expectation , storage(allocator.allocate(maxCapacity)) { vm::BeginOnDemandCommits(); } ~Storage_AutoCommit() { vm::EndOnDemandCommits(); allocator.deallocate(storage, maxCapacity); } uintptr_t Address() const { return uintptr_t(storage); } size_t Capacity() const { return maxCapacity; } size_t MaxCapacity() const { return maxCapacity; } bool Expand(size_t UNUSED(requiredCapacity)) { return false; } private: Allocator allocator; size_t maxCapacity; // must be initialized before storage void* storage; }; // reserve and return a pointer to space at the end of storage, // expanding it if need be. // @param end total number of previously reserved bytes; will be // increased by size if the allocation succeeds. // @param size [bytes] to reserve. // @return address of allocated space, or 0 if storage is full // and cannot expand any further. template static inline uintptr_t StorageAppend(Storage& storage, size_t& end, size_t size) { size_t newEnd = end + size; if(newEnd > storage.Capacity()) { if(!storage.Expand(newEnd)) // NB: may change storage.Address() return 0; } std::swap(end, newEnd); return storage.Address() + newEnd; } // invoke operator() on default-constructed instantiations of // Functor for reasonable combinations of Storage and their parameters. template class Functor> static void ForEachStorage() { Functor >()(); Functor > >()(); Functor > >()(); Functor > >()(); Functor, Growth_Linear<> > >()(); Functor, Growth_Exponential<> > >()(); Functor, Growth_Linear<> > >()(); Functor, Growth_Exponential<> > >()(); Functor >()(); } } // namespace Allocators #endif // #ifndef ALLOCATOR_POLICIES Index: ps/trunk/source/lib/allocators/freelist.h =================================================================== --- ps/trunk/source/lib/allocators/freelist.h (revision 24351) +++ ps/trunk/source/lib/allocators/freelist.h (revision 24352) @@ -1,64 +1,66 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INCLUDED_ALLOCATORS_FREELIST #define INCLUDED_ALLOCATORS_FREELIST +#include + // "freelist" is a pointer to the first unused element or a sentinel. // their memory holds a pointer to the previous element in the freelist // (or its own address in the case of sentinels to avoid branches) // // rationale for the function-based interface: a class encapsulating the // freelist pointer would force each header to include this header, // whereas this approach only requires a void* pointer and calling // mem_freelist_Sentinel from the implementation. // // these functions are inlined because allocation is sometimes time-critical. // @return the address of a sentinel element, suitable for initializing // a freelist pointer. subsequent mem_freelist_Detach on that freelist // will return 0. LIB_API void* mem_freelist_Sentinel(); static inline void mem_freelist_AddToFront(void*& freelist, void* el) { ASSERT(freelist != 0); ASSERT(el != 0); memcpy(el, &freelist, sizeof(void*)); freelist = el; } // @return 0 if the freelist is empty, else a pointer that had // previously been passed to mem_freelist_AddToFront. static inline void* mem_freelist_Detach(void*& freelist) { ASSERT(freelist != 0); void* prev_el; memcpy(&prev_el, freelist, sizeof(void*)); void* el = (freelist == prev_el)? 0 : freelist; freelist = prev_el; return el; } #endif // #ifndef INCLUDED_ALLOCATORS_FREELIST Index: ps/trunk/source/lib/debug_stl.cpp =================================================================== --- ps/trunk/source/lib/debug_stl.cpp (revision 24351) +++ ps/trunk/source/lib/debug_stl.cpp (revision 24352) @@ -1,607 +1,608 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * portable debugging helper functions specific to the STL. */ #include "precompiled.h" #include "lib/debug_stl.h" +#include "lib/regex.h" + +#include #include +#include #include #include -#include -#include - -#include "lib/regex.h" +#include static const StatusDefinition debugStlStatusDefinitions[] = { { ERR::STL_CNT_UNKNOWN, L"Unknown STL container type_name" }, { ERR::STL_CNT_UNSUPPORTED, L"Unsupported STL container" }, { ERR::STL_CNT_INVALID, L"Container type is known but contents are invalid" } }; STATUS_ADD_DEFINITIONS(debugStlStatusDefinitions); // used in debug_stl_simplify_name. // note: wcscpy_s is safe because replacement happens in-place and // is longer than (otherwise, we wouldn't be replacing). #define REPLACE(what, with)\ else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\ {\ src += ARRAY_SIZE(what)-1-1; /* see preincrement rationale*/\ wcscpy_s(dst, ARRAY_SIZE(with), (with));\ dst += ARRAY_SIZE(with)-1;\ } #define STRIP(what)\ else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\ {\ src += ARRAY_SIZE(what)-1-1;/* see preincrement rationale*/\ } #define STRIP_NESTED(what)\ else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\ {\ /* remove preceding comma (if present) */\ if(src != name && src[-1] == ',')\ dst--;\ src += ARRAY_SIZE(what)-1;\ /* strip everything until trailing > is matched */\ ENSURE(nesting == 0);\ nesting = 1;\ } // reduce complicated STL names to human-readable form (in place). // e.g. "std::basic_string, std::allocator >" => // "string". algorithm: strip undesired strings in one pass (fast). // called from symbol_string_build. // // see http://www.bdsoft.com/tools/stlfilt.html and // http://www.moderncppdesign.com/publications/better_template_error_messages.html wchar_t* debug_stl_simplify_name(wchar_t* name) { // used when stripping everything inside a < > to continue until // the final bracket is matched (at the original nesting level). int nesting = 0; const wchar_t* src = name-1; // preincremented; see below. wchar_t* dst = name; // for each character: (except those skipped as parts of strings) for(;;) { wchar_t c = *(++src); // preincrement rationale: src++ with no further changes would // require all comparisons to subtract 1. incrementing at the // end of a loop would require a goto, instead of continue // (there are several paths through the loop, for speed). // therefore, preincrement. when skipping strings, subtract // 1 from the offset (since src is advanced directly after). // end of string reached - we're done. if(c == '\0') { *dst = '\0'; break; } // we're stripping everything inside a < >; eat characters // until final bracket is matched (at the original nesting level). if(nesting) { if(c == '<') nesting++; else if(c == '>') { nesting--; ENSURE(nesting >= 0); } continue; } // start if chain (REPLACE and STRIP use else if) if(0) {} else if(!wcsncmp(src, L"::_Node", 7)) { // add a space if not already preceded by one // (prevents replacing ">::_Node>" with ">>") if(src != name && src[-1] != ' ') *dst++ = ' '; src += 7; } REPLACE(L"unsigned short", L"u16") REPLACE(L"unsigned int", L"size_t") REPLACE(L"unsigned __int64", L"u64") STRIP(L",0> ") // early out: all tests after this start with s, so skip them else if(c != 's') { *dst++ = c; continue; } REPLACE(L"std::_List_nod", L"list") REPLACE(L"std::_Tree_nod", L"map") REPLACE(L"std::basic_string, ") STRIP(L"std::char_traits, ") STRIP(L"std::char_traits<__wchar_t>, ") STRIP(L"std::_Tmap_traits") STRIP(L"std::_Tset_traits") STRIP_NESTED(L"std::allocator<") STRIP_NESTED(L"std::less<") STRIP_NESTED(L"stdext::hash_compare<") STRIP(L"std::") STRIP(L"stdext::") else *dst++ = c; } return name; } //----------------------------------------------------------------------------- // STL container debugging //----------------------------------------------------------------------------- // provide an iterator interface for arbitrary STL containers; this is // used to display their contents in stack traces. their type and // contents aren't known until runtime, so this is somewhat tricky. // // we assume STL containers aren't specialized on their content type and // use their int instantiations's memory layout. vector will therefore // not be displayed correctly, but it is frowned upon anyway (since // address of its elements can't be taken). // to be 100% correct, we'd have to write an Any_container_type__element_type // class for each combination, but that is clearly infeasible. // // containers might still be uninitialized when we call get_container_info on // them. we need to check if they are IsValid and only then use their contents. // to that end, we derive a validator class from each container, // cast the container's address to it, and call its IsValid() method. // // checks performed include: is size() realistic; does begin() come before // end(), etc. we need to leverage all invariants because the values are // random in release mode. // // we sometimes need to access protected members of the STL containers. // granting access via friend is not possible since the system headers // must not be changed. that leaves us with two alternatives: // 1) write a 'shadow' class that has the same memory layout. this would // free us from the ugly Dinkumware naming conventions, but requires // more maintenance when the STL implementation changes. // 2) derive from the container. while not entirely bulletproof due to the // lack of virtual dtors, this is safe in practice because pointers are // neither returned to users nor freed. the only requirement is that // classes must not include virtual functions, because a vptr would // change the memory layout in unknown ways. // // it is rather difficult to abstract away implementation details of various // STL versions. we currently only really support Dinkumware due to // significant differences in the implementations of set, map and string. //---------------------------------------------------------------------------- // standard containers // base class (slightly simplifies code by providing default implementations // that can be used for most containers). // Container is the complete type of the STL container (we can't pass this // as a template because Dinkumware _Tree requires different parameters) template struct ContainerBase : public Container { bool IsValid(size_t UNUSED(el_size)) const { return true; } size_t NumElements(size_t UNUSED(el_size)) const { return this->size(); } static const u8* DereferenceAndAdvance(typename Container::iterator& it, size_t UNUSED(el_size)) { const u8* p = (const u8*)&*it; ++it; return p; } }; struct Any_deque : public ContainerBase > { #if STL_DINKUMWARE == 405 bool IsValid(size_t el_size) const { const size_t el_per_bucket = ElementsPerBucket(el_size); // initial element is beyond end of first bucket if(_Myoff >= el_per_bucket) return false; // more elements reported than fit in all buckets if(_Mysize > _Mapsize * el_per_bucket) return false; return true; } static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size) { struct Iterator : public iterator { Any_deque& Container() const { return *(Any_deque*)_Mycont; } size_t CurrentIndex() const { return _Myoff; } }; Iterator& it = *(Iterator*)&stl_it; Any_deque& container = it.Container(); const size_t currentIndex = it.CurrentIndex(); const u8* p = container.GetNthElement(currentIndex, el_size); ++it; return p; } private: static size_t ElementsPerBucket(size_t el_size) { return std::max(16u / el_size, (size_t)1u); // see _DEQUESIZ } const u8* GetNthElement(size_t i, size_t el_size) const { const size_t el_per_bucket = ElementsPerBucket(el_size); const size_t bucket_idx = i / el_per_bucket; ENSURE(bucket_idx < _Mapsize); const size_t idx_in_bucket = i - bucket_idx * el_per_bucket; ENSURE(idx_in_bucket < el_per_bucket); const u8** map = (const u8**)_Map; const u8* bucket = map[bucket_idx]; const u8* p = bucket + idx_in_bucket*el_size; return p; } #endif }; struct Any_list : public ContainerBase > { }; #if STL_DINKUMWARE == 405 template struct Any_tree : public std::_Tree<_Traits> { Any_tree() // (required because default ctor cannot be generated) { } bool IsValid(size_t UNUSED(el_size)) const { return true; } size_t NumElements(size_t UNUSED(el_size)) const { return size(); } static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size) { struct Iterator : public const_iterator { _Nodeptr Node() const { return _Ptr; } void SetNode(_Nodeptr node) { _Ptr = node; } }; Iterator& it = *(Iterator*)&stl_it; _Nodeptr node = it.Node(); const u8* p = (const u8*)&*it; // end() shouldn't be incremented, don't move if(_Isnil(node, el_size)) return p; // return smallest (leftmost) node of right subtree _Nodeptr _Pnode = _Right(node); if(!_Isnil(_Pnode, el_size)) { while(!_Isnil(_Left(_Pnode), el_size)) _Pnode = _Left(_Pnode); } // climb looking for right subtree else { while (!_Isnil(_Pnode = _Parent(node), el_size) && node == _Right(_Pnode)) node = _Pnode; // ==> parent while right subtree } it.SetNode(_Pnode); return p; }; private: // return reference to the given node's nil flag. // reimplemented because this member is stored after _Myval, so it's // dependent on el_size. static _Charref _Isnil(_Nodeptr _Pnode, size_t el_size) { const u8* p = (const u8*)&_Pnode->_Isnil; // correct for int specialization p += el_size - sizeof(value_type); // adjust for difference in el_size assert(*p <= 1); // bool value return (_Charref)*p; } }; struct Any_map : public Any_tree, std::allocator >, false> > { }; struct Any_multimap : public Any_map { }; struct Any_set: public Any_tree, std::allocator, false> > { }; struct Any_multiset: public Any_set { }; #endif struct Any_vector: public ContainerBase > { bool IsValid(size_t UNUSED(el_size)) const { // more elements reported than reserved if(size() > capacity()) return false; // front/back pointers incorrect if(&front() > &back()) return false; return true; } #if STL_DINKUMWARE == 405 size_t NumElements(size_t el_size) const { // vectors store front and back pointers and calculate their // element count as the difference between them. since we are // derived from a template specialization, the pointer arithmetic // is incorrect. we fix it by taking el_size into account. return ((u8*)_Mylast - (u8*)_Myfirst) * el_size; } static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size) { struct Iterator : public const_iterator { void Advance(size_t numBytes) { (u8*&)_Myptr += numBytes; } }; Iterator& it = *(Iterator*)&stl_it; const u8* p = (const u8*)&*it; it.Advance(el_size); return p; } #endif }; #if STL_DINKUMWARE == 405 struct Any_basic_string : public ContainerBase { bool IsValid(size_t el_size) const { // less than the small buffer reserved - impossible if(_Myres < (16/el_size)-1) return false; // more elements reported than reserved if(_Mysize > _Myres) return false; return true; } }; #endif // // standard container adapters // // debug_stl_get_container_info makes sure this was actually instantiated with // container = deque as we assume. struct Any_queue : public Any_deque { }; // debug_stl_get_container_info makes sure this was actually instantiated with // container = deque as we assume. struct Any_stack : public Any_deque { }; //----------------------------------------------------------------------------- // generic iterator - returns next element. dereferences and increments the // specific container iterator stored in it_mem. template const u8* stl_iterator(void* it_mem, size_t el_size) { typedef typename T::iterator iterator; iterator& stl_it = *(iterator*)it_mem; return T::DereferenceAndAdvance(stl_it, el_size); } // basic sanity checks that apply to all containers. template static bool IsContainerValid(const T& t, size_t el_count) { // note: don't test empty() because vector's implementation of it // depends on el_size. // size must be reasonable if(el_count > 0x1000000) return false; if(el_count != 0) { // valid pointer const u8* front = (const u8*)&*t.begin(); // (note: map doesn't have front) if(debug_IsPointerBogus(front)) return false; // note: don't test back() because that depends on el_size and // requires container-specific code. } return true; } // check if the container is IsValid and return # elements and an iterator; // this is instantiated once for each type of container. // we don't do this in the Any_* ctors because we need to return bool IsValid and // don't want to throw an exception (may confuse the debug code). template bool get_container_info(const T& t, size_t size, size_t el_size, size_t& el_count, DebugStlIterator& el_iterator, void* it_mem) { typedef typename T::iterator iterator; typedef typename T::const_iterator const_iterator; ENSURE(sizeof(T) == size); ENSURE(sizeof(iterator) < DEBUG_STL_MAX_ITERATOR_SIZE); el_count = t.NumElements(el_size); // bail if the container is uninitialized/invalid. if(!IsContainerValid(t, el_count)) return false; el_iterator = stl_iterator; // construct a copy of begin() at it_mem. placement new is necessary // because VC8's secure copy ctor apparently otherwise complains about // invalid values in the (uninitialized) destination memory. new(it_mem) const_iterator(t.begin()); return true; } // if indicates the object to be an STL container, // and given the size of its value_type (retrieved via debug information), // return number of elements and an iterator (any data it needs is stored in // it_mem, which must hold DEBUG_STL_MAX_ITERATOR_SIZE bytes). // returns 0 on success or an StlContainerError. Status debug_stl_get_container_info(const wchar_t* type_name, const u8* p, size_t size, size_t el_size, size_t* el_count, DebugStlIterator* el_iterator, void* it_mem) { #if MSC_VERSION UNUSED2(type_name); UNUSED2(p); UNUSED2(size); UNUSED2(el_size); UNUSED2(el_count); UNUSED2(el_iterator); UNUSED2(it_mem); return ERR::STL_CNT_UNSUPPORTED; // NOWARN #else bool handled = false, IsValid = false; #define CONTAINER(name, type_name_pattern)\ else if(match_wildcard(type_name, type_name_pattern))\ {\ handled = true;\ IsValid = get_container_info(*(Any_##name*)p, size, el_size, *el_count, *el_iterator, it_mem);\ } #define STD_CONTAINER(name) CONTAINER(name, L"std::" WIDEN(#name) L"<*>") // workaround for preprocessor limitation: what we're trying to do is // stringize the defined value of a macro. prepending and pasting L // apparently isn't possible because macro args aren't expanded before // being pasted; we therefore compare as chars[]. #define STRINGIZE2(id) # id #define STRINGIZE(id) STRINGIZE2(id) if(0) {} // kickoff // standard containers STD_CONTAINER(deque) STD_CONTAINER(list) STD_CONTAINER(vector) #if STL_DINKUMWARE == 405 STD_CONTAINER(map) STD_CONTAINER(multimap) STD_CONTAINER(set) STD_CONTAINER(multiset) STD_CONTAINER(basic_string) #endif // standard container adapters // (note: Any_queue etc. assumes the underlying container is a deque. // we make sure of that here and otherwise refuse to display it, because // doing so is lots of work for little gain.) CONTAINER(queue, L"std::queue<*,std::deque<*> >") CONTAINER(stack, L"std::stack<*,std::deque<*> >") // note: do not raise warnings - these can happen for new // STL classes or if the debuggee's memory is corrupted. if(!handled) return ERR::STL_CNT_UNKNOWN; // NOWARN if(!IsValid) return ERR::STL_CNT_INVALID; // NOWARN return INFO::OK; #endif } Index: ps/trunk/source/lib/file/file_system.h =================================================================== --- ps/trunk/source/lib/file/file_system.h (revision 24351) +++ ps/trunk/source/lib/file/file_system.h (revision 24352) @@ -1,90 +1,91 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * higher-level interface on top of sysdep/filesystem.h */ #ifndef INCLUDED_FILE_SYSTEM #define INCLUDED_FILE_SYSTEM #include "lib/os_path.h" #include "lib/posix/posix_filesystem.h" // mode_t +#include LIB_API bool DirectoryExists(const OsPath& path); LIB_API bool FileExists(const OsPath& pathname); LIB_API u64 FileSize(const OsPath& pathname); // (bundling size and mtime avoids a second expensive call to stat()) class CFileInfo { public: CFileInfo() { } CFileInfo(const OsPath& name, off_t size, time_t mtime) : name(name), size(size), mtime(mtime) { } const OsPath& Name() const { return name; } off_t Size() const { return size; } time_t MTime() const { return mtime; } private: OsPath name; off_t size; time_t mtime; }; LIB_API Status GetFileInfo(const OsPath& pathname, CFileInfo* fileInfo); typedef std::vector CFileInfos; typedef std::vector DirectoryNames; LIB_API Status GetDirectoryEntries(const OsPath& path, CFileInfos* files, DirectoryNames* subdirectoryNames); // same as boost::filesystem::create_directories, except that mkdir is invoked with // instead of 0755. // If the breakpoint is enabled, debug_break will be called if the directory didn't exist and couldn't be created. LIB_API Status CreateDirectories(const OsPath& path, mode_t mode, bool breakpoint = true); LIB_API Status DeleteDirectory(const OsPath& dirPath); LIB_API Status CopyFile(const OsPath& path, const OsPath& newPath, bool override_if_exists = false); #endif // #ifndef INCLUDED_FILE_SYSTEM Index: ps/trunk/source/graphics/HeightMipmap.h =================================================================== --- ps/trunk/source/graphics/HeightMipmap.h (revision 24351) +++ ps/trunk/source/graphics/HeightMipmap.h (revision 24352) @@ -1,80 +1,82 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ /* * Describes ground using heightmap mipmaps * Used for camera movement */ #ifndef INCLUDED_HEIGHTMIPMAP #define INCLUDED_HEIGHTMIPMAP +#include + class Path; using VfsPath = Path; struct SMipmap { SMipmap() : m_MapSize(0), m_Heightmap(0) { } SMipmap(size_t MapSize, u16* Heightmap) : m_MapSize(MapSize), m_Heightmap(Heightmap) { } size_t m_MapSize; u16* m_Heightmap; }; class CHeightMipmap { NONCOPYABLE(CHeightMipmap); public: CHeightMipmap(); ~CHeightMipmap(); void Initialize(size_t mapSize, const u16* ptr); void ReleaseData(); // update the heightmap mipmaps void Update(const u16* ptr); // update a section of the heightmap mipmaps // (coordinates are heightmap cells, inclusive of lower bounds, // exclusive of upper bounds) void Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); float GetTrilinearGroundLevel(float x, float z, float radius) const; void DumpToDisk(const VfsPath& path) const; private: // get bilinear filtered height from mipmap float BilinearFilter(const SMipmap &mipmap, float x, float z) const; // update rectangle of the output mipmap by bilinear interpolating an input mipmap of exactly twice its size void HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); // update rectangle of the output mipmap by bilinear interpolating the input mipmap void BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); // size of this map in each direction size_t m_MapSize; // mipmap list std::vector m_Mipmap; }; #endif Index: ps/trunk/source/graphics/MiniPatch.h =================================================================== --- ps/trunk/source/graphics/MiniPatch.h (revision 24351) +++ ps/trunk/source/graphics/MiniPatch.h (revision 24352) @@ -1,47 +1,47 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ /* * Definition of a single terrain tile */ #ifndef INCLUDED_MINIPATCH #define INCLUDED_MINIPATCH #include "lib/res/handle.h" -#include "graphics/TerrainTextureEntry.h" +class CTerrainTextureEntry; /////////////////////////////////////////////////////////////////////////////// // CMiniPatch: definition of a single terrain tile class CMiniPatch { public: // constructor CMiniPatch(); // texture applied to tile CTerrainTextureEntry* Tex; // 'priority' of the texture - determines drawing order of terrain textures int Priority; CTerrainTextureEntry* GetTextureEntry() { return Tex; } int GetPriority() { return Priority; } }; #endif Index: ps/trunk/source/graphics/Overlay.h =================================================================== --- ps/trunk/source/graphics/Overlay.h (revision 24351) +++ ps/trunk/source/graphics/Overlay.h (revision 24352) @@ -1,199 +1,201 @@ /* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_GRAPHICS_OVERLAY #define INCLUDED_GRAPHICS_OVERLAY #include "graphics/Color.h" #include "graphics/Texture.h" #include "maths/Vector2D.h" #include "maths/Vector3D.h" #include "maths/FixedVector3D.h" #include "ps/CStrIntern.h" +#include + class CFrustum; class CTerrain; class CSimContext; class CTexturedLineRData; struct SOverlayDescriptor; /** * Line-based overlay, with world-space coordinates, rendered in the world * potentially behind other objects. Designed for selection circles and debug info. */ struct SOverlayLine { SOverlayLine() : m_Thickness(1) { } CColor m_Color; std::vector m_Coords; // (x, y, z) vertex coordinate triples; shape is not automatically closed u8 m_Thickness; // in pixels void PushCoords(const CVector3D& v) { PushCoords(v.X, v.Y, v.Z); } void PushCoords(const float x, const float y, const float z) { m_Coords.push_back(x); m_Coords.push_back(y); m_Coords.push_back(z); } }; /** * Textured line overlay, with world-space coordinates, rendered in the world onto the terrain. * Designed for relatively static textured lines, i.e. territory borders, originally. * * Once submitted for rendering, instances must not be copied afterwards. The reason is that they * are assigned rendering data that is unique to the submitted instance, and non-transferable to * any copies that would otherwise be made. Amongst others, this restraint includes that they must * not be submitted by their address inside a std::vector storing them by value. */ struct SOverlayTexturedLine { enum LineCapType { LINECAP_FLAT, ///< no line ending; abrupt stop of the line (aka. butt ending) /** * Semi-circular line ending. The texture is mapped by curving the left vertical edge * around the semi-circle's rim. That is, the center point has UV coordinates (0.5;0.5), * and the rim vertices all have U coordinate 0 and a V coordinate that ranges from 0 to * 1 as the rim is traversed. */ LINECAP_ROUND, LINECAP_SHARP, ///< sharp point ending LINECAP_SQUARE, ///< square end that extends half the line width beyond the line end }; SOverlayTexturedLine() : m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), m_StartCapType(LINECAP_FLAT), m_EndCapType(LINECAP_FLAT), m_SimContext(NULL) { } CTexturePtr m_TextureBase; CTexturePtr m_TextureMask; /// Color to apply to the line texture, where indicated by the mask. CColor m_Color; /// (x, z) vertex coordinate pairs; y is computed automatically. std::vector m_Coords; /// Half-width of the line, in world-space units. float m_Thickness; /// Should this line be treated as a closed loop? If set, any end cap settings are ignored. bool m_Closed; /// Should this line be rendered fully visible at all times, even under the SoD? bool m_AlwaysVisible; LineCapType m_StartCapType; LineCapType m_EndCapType; /** * Simulation context applicable for this overlay line; used to obtain terrain information * during automatic computation of Y coordinates. */ const CSimContext* m_SimContext; /** * Cached renderer data, because expensive to compute. Allocated by the renderer when necessary * for rendering purposes. * * Note: the rendering data may be shared between copies of this object to prevent having to * recompute it, while at the same time maintaining copyability of this object (see also docs on * CTexturedLineRData). */ shared_ptr m_RenderData; /** * Converts a string line cap type into its corresponding LineCap enum value, and returns * the resulting value. If the input string is unrecognized, a warning is issued and a * default value is returned. */ static LineCapType StrToLineCapType(const std::wstring& str); /** * Creates the texture specified by the given overlay descriptor and assigns it to this overlay. */ void CreateOverlayTexture(const SOverlayDescriptor* overlayDescriptor); void PushCoords(const float x, const float z) { m_Coords.emplace_back(x, z); } void PushCoords(const CVector2D& v) { m_Coords.push_back(v); } void PushCoords(const std::vector& points) { for (const CVector2D& point : points) PushCoords(point); } bool IsVisibleInFrustum(const CFrustum& frustum) const; }; /** * Billboard sprite overlay, with world-space coordinates, rendered on top * of all other objects. Designed for health bars and rank icons. */ struct SOverlaySprite { CTexturePtr m_Texture; CColor m_Color; CVector3D m_Position; // base position float m_X0, m_Y0, m_X1, m_Y1; // billboard corner coordinates, relative to base position }; /** * Rectangular single-quad terrain overlay, in world space coordinates. The vertices of the quad * are not required to be coplanar; the quad is arbitrarily triangulated with no effort being made * to find a best fit to the underlying terrain. */ struct SOverlayQuad { CTexturePtr m_Texture; CTexturePtr m_TextureMask; CVector3D m_Corners[4]; CColor m_Color; }; struct SOverlaySphere { SOverlaySphere() : m_Radius(0) { } CVector3D m_Center; float m_Radius; CColor m_Color; }; enum EOverlayType { /// A single textured quad overlay, intended for entities that move around much, like units (e.g. foot soldiers, etc). DYNAMIC_QUAD, /// A more complex textured line overlay, composed of several textured line segments. STATIC_OUTLINE, }; struct SOverlayDescriptor { EOverlayType m_Type; CStrIntern m_QuadTexture; CStrIntern m_QuadTextureMask; CStrIntern m_LineTexture; CStrIntern m_LineTextureMask; float m_LineThickness; int m_Radius; SOverlayDescriptor() : m_LineThickness(0) { } }; // TODO: OverlayText #endif // INCLUDED_GRAPHICS_OVERLAY Index: ps/trunk/source/graphics/ShaderDefines.h =================================================================== --- ps/trunk/source/graphics/ShaderDefines.h (revision 24351) +++ ps/trunk/source/graphics/ShaderDefines.h (revision 24352) @@ -1,233 +1,234 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_SHADERDEFINES #define INCLUDED_SHADERDEFINES #include "graphics/ShaderProgramPtr.h" #include "ps/CStr.h" #include "ps/CStrIntern.h" #include #include +#include class CVector4D; /** * Represents a mapping of name strings to value, for use with * CShaderDefines (values are strings) and CShaderUniforms (values are vec4s). * * Stored as interned vectors of name-value pairs, to support high performance * comparison operators. * * Not thread-safe - must only be used from the main thread. */ template class CShaderParams { public: /** * Create an empty map of defines. */ CShaderParams(); /** * Add a name and associated value to the map of parameters. * If the name is already defined, its value will be replaced. */ void Set(CStrIntern name, const value_t& value); /** * Add all the names and values from another set of parameters. * If any name is already defined in this object, its value will be replaced. */ void SetMany(const CShaderParams& params); /** * Return a copy of the current name/value mapping. */ std::map GetMap() const; /** * Return a hash of the current mapping. */ size_t GetHash() const; /** * Compare with some arbitrary total order. * The order may be different each time the application is run * (it is based on interned memory addresses). */ bool operator<(const CShaderParams& b) const { return m_Items < b.m_Items; } /** * Fast equality comparison. */ bool operator==(const CShaderParams& b) const { return m_Items == b.m_Items; } /** * Fast inequality comparison. */ bool operator!=(const CShaderParams& b) const { return m_Items != b.m_Items; } struct SItems { // Name/value pair using Item = std::pair; // Sorted by name; no duplicated names std::vector items; size_t hash; void RecalcHash(); }; struct SItemsHash { std::size_t operator()(const SItems& items) const { return items.hash; } }; protected: SItems* m_Items; // interned value private: using InternedItems_t = std::unordered_map, SItemsHash >; static InternedItems_t s_InternedItems; /** * Returns a pointer to an SItems equal to @p items. * The pointer will be valid forever, and the same pointer will be returned * for any subsequent requests for an equal items list. */ static SItems* GetInterned(const SItems& items); CShaderParams(SItems* items); static CShaderParams CreateEmpty(); static CShaderParams s_Empty; }; /** * Represents a mapping of name strings to value strings, for use with * \#if and \#ifdef and similar conditionals in shaders. * * Not thread-safe - must only be used from the main thread. */ class CShaderDefines : public CShaderParams { public: /** * Add a name and associated value to the map of defines. * If the name is already defined, its value will be replaced. */ void Add(CStrIntern name, CStrIntern value); /** * Return the value for the given name as an integer, or 0 if not defined. */ int GetInt(const char* name) const; }; /** * Represents a mapping of name strings to value CVector4Ds, for use with * uniforms in shaders. * * Not thread-safe - must only be used from the main thread. */ class CShaderUniforms : public CShaderParams { public: /** * Add a name and associated value to the map of uniforms. * If the name is already defined, its value will be replaced. */ void Add(const char* name, const CVector4D& value); /** * Return the value for the given name, or (0,0,0,0) if not defined. */ CVector4D GetVector(const char* name) const; /** * Bind the collection of uniforms onto the given shader. */ void BindUniforms(const CShaderProgramPtr& shader) const; }; // Add here the types of queries we can make in the renderer enum RENDER_QUERIES { RQUERY_TIME, RQUERY_WATER_TEX, RQUERY_SKY_CUBE }; /** * Uniform values that need to be evaluated in the renderer. * * Not thread-safe - must only be used from the main thread. */ class CShaderRenderQueries { public: using RenderQuery = std::pair; void Add(const char* name); size_t GetSize() const { return m_Items.size(); } RenderQuery GetItem(size_t i) const { return m_Items[i]; } private: std::vector m_Items; }; enum DEFINE_CONDITION_TYPES { DCOND_DISTANCE }; class CShaderConditionalDefines { public: struct CondDefine { CStrIntern m_DefName; CStrIntern m_DefValue; int m_CondType; std::vector m_CondArgs; }; void Add(const char* defname, const char* defvalue, int type, std::vector &args); size_t GetSize() const { return m_Defines.size(); } const CondDefine& GetItem(size_t i) const { return m_Defines[i]; } private: std::vector m_Defines; }; #endif // INCLUDED_SHADERDEFINES Index: ps/trunk/source/graphics/ShaderProgram.cpp =================================================================== --- ps/trunk/source/graphics/ShaderProgram.cpp (revision 24351) +++ ps/trunk/source/graphics/ShaderProgram.cpp (revision 24352) @@ -1,892 +1,893 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "ShaderProgram.h" #include "graphics/Color.h" #include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" +#include "lib/timer.h" #include "lib/res/graphics/ogl_tex.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #if !CONFIG2_GLES class CShaderProgramARB : public CShaderProgram { public: CShaderProgramARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags) : CShaderProgram(streamflags), m_VertexFile(vertexFile), m_FragmentFile(fragmentFile), m_Defines(defines), m_VertexIndexes(vertexIndexes), m_FragmentIndexes(fragmentIndexes) { pglGenProgramsARB(1, &m_VertexProgram); pglGenProgramsARB(1, &m_FragmentProgram); } ~CShaderProgramARB() { Unload(); pglDeleteProgramsARB(1, &m_VertexProgram); pglDeleteProgramsARB(1, &m_FragmentProgram); } bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code) { ogl_WarnIfError(); pglBindProgramARB(target, program); ogl_WarnIfError(); pglProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str()); if (ogl_SquelchError(GL_INVALID_OPERATION)) { GLint errPos = 0; glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos); int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1; char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr); return false; } pglBindProgramARB(target, 0); ogl_WarnIfError(); return true; } virtual void Reload() { Unload(); CVFSFile vertexFile; if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK) return; CVFSFile fragmentFile; if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK) return; CPreprocessorWrapper preprocessor; preprocessor.AddDefines(m_Defines); CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString()); CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString()); // printf(">>>\n%s<<<\n", vertexCode.c_str()); // printf(">>>\n%s<<<\n", fragmentCode.c_str()); if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, m_VertexFile, vertexCode)) return; if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, m_FragmentFile, fragmentCode)) return; m_IsValid = true; } void Unload() { m_IsValid = false; } virtual void Bind() { glEnable(GL_VERTEX_PROGRAM_ARB); glEnable(GL_FRAGMENT_PROGRAM_ARB); pglBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram); pglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram); BindClientStates(); } virtual void Unbind() { glDisable(GL_VERTEX_PROGRAM_ARB); glDisable(GL_FRAGMENT_PROGRAM_ARB); pglBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0); pglBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); UnbindClientStates(); // TODO: should unbind textures, probably } int GetUniformVertexIndex(CStrIntern id) { std::map::iterator it = m_VertexIndexes.find(id); if (it == m_VertexIndexes.end()) return -1; return it->second; } frag_index_pair_t GetUniformFragmentIndex(CStrIntern id) { std::map::iterator it = m_FragmentIndexes.find(id); if (it == m_FragmentIndexes.end()) return std::make_pair(-1, 0); return it->second; } virtual Binding GetTextureBinding(texture_id_t id) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index == -1) return Binding(); else return Binding((int)fPair.second, index); } virtual void BindTexture(texture_id_t id, Handle tex) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index != -1) { GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0+index); glBindTexture(fPair.second, h); } } virtual void BindTexture(texture_id_t id, GLuint tex) { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index != -1) { pglActiveTextureARB(GL_TEXTURE0+index); glBindTexture(fPair.second, tex); } } virtual void BindTexture(Binding id, Handle tex) { int index = id.second; if (index != -1) ogl_tex_bind(tex, index); } virtual Binding GetUniformBinding(uniform_id_t id) { return Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id).first); } virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) { if (id.first != -1) pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first, v0, v1, v2, v3); if (id.second != -1) pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second, v0, v1, v2, v3); } virtual void Uniform(Binding id, const CMatrix3D& v) { if (id.first != -1) { pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+0, v._11, v._12, v._13, v._14); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+1, v._21, v._22, v._23, v._24); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+2, v._31, v._32, v._33, v._34); pglProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+3, v._41, v._42, v._43, v._44); } if (id.second != -1) { pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+0, v._11, v._12, v._13, v._14); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+1, v._21, v._22, v._23, v._24); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+2, v._31, v._32, v._33, v._34); pglProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+3, v._41, v._42, v._43, v._44); } } virtual void Uniform(Binding id, size_t count, const CMatrix3D* v) { ENSURE(count == 1); Uniform(id, v[0]); } private: VfsPath m_VertexFile; VfsPath m_FragmentFile; CShaderDefines m_Defines; GLuint m_VertexProgram; GLuint m_FragmentProgram; std::map m_VertexIndexes; // pair contains std::map m_FragmentIndexes; }; #endif // #if !CONFIG2_GLES ////////////////////////////////////////////////////////////////////////// TIMER_ADD_CLIENT(tc_ShaderGLSLCompile); TIMER_ADD_CLIENT(tc_ShaderGLSLLink); class CShaderProgramGLSL : public CShaderProgram { public: CShaderProgramGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) : CShaderProgram(streamflags), m_VertexFile(vertexFile), m_FragmentFile(fragmentFile), m_Defines(defines), m_VertexAttribs(vertexAttribs) { m_Program = 0; m_VertexShader = pglCreateShaderObjectARB(GL_VERTEX_SHADER); m_FragmentShader = pglCreateShaderObjectARB(GL_FRAGMENT_SHADER); } ~CShaderProgramGLSL() { Unload(); pglDeleteShader(m_VertexShader); pglDeleteShader(m_FragmentShader); } bool Compile(GLhandleARB shader, const VfsPath& file, const CStr& code) { TIMER_ACCRUE(tc_ShaderGLSLCompile); ogl_WarnIfError(); const char* code_string = code.c_str(); GLint code_length = code.length(); pglShaderSourceARB(shader, 1, &code_string, &code_length); pglCompileShaderARB(shader); GLint ok = 0; pglGetShaderiv(shader, GL_COMPILE_STATUS, &ok); GLint length = 0; pglGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); // Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0 // (http://code.google.com/p/android/issues/detail?id=9953) if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; pglGetShaderInfoLog(shader, length, NULL, infolog); if (ok) LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog); else LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); return (ok ? true : false); } bool Link() { TIMER_ACCRUE(tc_ShaderGLSLLink); ENSURE(!m_Program); m_Program = pglCreateProgramObjectARB(); pglAttachObjectARB(m_Program, m_VertexShader); ogl_WarnIfError(); pglAttachObjectARB(m_Program, m_FragmentShader); ogl_WarnIfError(); // Set up the attribute bindings explicitly, since apparently drivers // don't always pick the most efficient bindings automatically, // and also this lets us hardcode indexes into VertexPointer etc for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) pglBindAttribLocationARB(m_Program, it->second, it->first.c_str()); pglLinkProgramARB(m_Program); GLint ok = 0; pglGetProgramiv(m_Program, GL_LINK_STATUS, &ok); GLint length = 0; pglGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length); if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; pglGetProgramInfoLog(m_Program, length, NULL, infolog); if (ok) LOGMESSAGE("Info when linking program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog); else LOGERROR("Failed to link program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); if (!ok) return false; m_Uniforms.clear(); m_Samplers.clear(); Bind(); ogl_WarnIfError(); GLint numUniforms = 0; pglGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms); ogl_WarnIfError(); for (GLint i = 0; i < numUniforms; ++i) { char name[256] = {0}; GLsizei nameLength = 0; GLint size = 0; GLenum type = 0; pglGetActiveUniformARB(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name); ogl_WarnIfError(); GLint loc = pglGetUniformLocationARB(m_Program, name); CStrIntern nameIntern(name); m_Uniforms[nameIntern] = std::make_pair(loc, type); // Assign sampler uniforms to sequential texture units if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE #if !CONFIG2_GLES || type == GL_SAMPLER_2D_SHADOW #endif ) { int unit = (int)m_Samplers.size(); m_Samplers[nameIntern].first = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D); m_Samplers[nameIntern].second = unit; pglUniform1iARB(loc, unit); // link uniform to unit ogl_WarnIfError(); } } // TODO: verify that we're not using more samplers than is supported Unbind(); ogl_WarnIfError(); return true; } virtual void Reload() { Unload(); CVFSFile vertexFile; if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK) return; CVFSFile fragmentFile; if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK) return; CPreprocessorWrapper preprocessor; preprocessor.AddDefines(m_Defines); #if CONFIG2_GLES // GLES defines the macro "GL_ES" in its GLSL preprocessor, // but since we run our own preprocessor first, we need to explicitly // define it here preprocessor.AddDefine("GL_ES", "1"); #endif CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString()); CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString()); #if CONFIG2_GLES // Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00, // and also to set default float precision for fragment shaders vertexCode.Replace("#version 110\n", "#version 100\n"); vertexCode.Replace("#version 110\r\n", "#version 100\n"); vertexCode.Replace("#version 120\n", "#version 100\n"); vertexCode.Replace("#version 120\r\n", "#version 100\n"); fragmentCode.Replace("#version 110\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 110\r\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\r\n", "#version 100\nprecision mediump float;\n"); #endif if (!Compile(m_VertexShader, m_VertexFile, vertexCode)) return; if (!Compile(m_FragmentShader, m_FragmentFile, fragmentCode)) return; if (!Link()) return; m_IsValid = true; } void Unload() { m_IsValid = false; if (m_Program) pglDeleteProgram(m_Program); m_Program = 0; // The shader objects can be reused and don't need to be deleted here } virtual void Bind() { pglUseProgramObjectARB(m_Program); for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) pglEnableVertexAttribArrayARB(it->second); } virtual void Unbind() { pglUseProgramObjectARB(0); for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) pglDisableVertexAttribArrayARB(it->second); // TODO: should unbind textures, probably } virtual Binding GetTextureBinding(texture_id_t id) { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return Binding(); else return Binding((int)it->second.first, it->second.second); } virtual void BindTexture(texture_id_t id, Handle tex) { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return; GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0 + it->second.second); glBindTexture(it->second.first, h); } virtual void BindTexture(texture_id_t id, GLuint tex) { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return; pglActiveTextureARB(GL_TEXTURE0 + it->second.second); glBindTexture(it->second.first, tex); } virtual void BindTexture(Binding id, Handle tex) { if (id.second == -1) return; GLuint h; ogl_tex_get_texture_id(tex, &h); pglActiveTextureARB(GL_TEXTURE0 + id.second); glBindTexture(id.first, h); } virtual Binding GetUniformBinding(uniform_id_t id) { std::map >::iterator it = m_Uniforms.find(id); if (it == m_Uniforms.end()) return Binding(); else return Binding(it->second.first, (int)it->second.second); } virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) { if (id.first != -1) { if (id.second == GL_FLOAT) pglUniform1fARB(id.first, v0); else if (id.second == GL_FLOAT_VEC2) pglUniform2fARB(id.first, v0, v1); else if (id.second == GL_FLOAT_VEC3) pglUniform3fARB(id.first, v0, v1, v2); else if (id.second == GL_FLOAT_VEC4) pglUniform4fARB(id.first, v0, v1, v2, v3); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float, vec2, vec3, vec4)"); } } virtual void Uniform(Binding id, const CMatrix3D& v) { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) pglUniformMatrix4fvARB(id.first, 1, GL_FALSE, &v._11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } virtual void Uniform(Binding id, size_t count, const CMatrix3D* v) { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) pglUniformMatrix4fvARB(id.first, count, GL_FALSE, &v->_11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } // Map the various fixed-function Pointer functions onto generic vertex attributes // (matching the attribute indexes from ShaderManager's ParseAttribSemantics): virtual void VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(0, size, type, GL_FALSE, stride, pointer); m_ValidStreams |= STREAM_POS; } virtual void NormalPointer(GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(2, 3, type, GL_TRUE, stride, pointer); m_ValidStreams |= STREAM_NORMAL; } virtual void ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(3, size, type, GL_TRUE, stride, pointer); m_ValidStreams |= STREAM_COLOR; } virtual void TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) { pglVertexAttribPointerARB(8 + texture - GL_TEXTURE0, size, type, GL_FALSE, stride, pointer); m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0); } virtual void VertexAttribPointer(attrib_id_t id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) { std::map::iterator it = m_VertexAttribs.find(id); if (it != m_VertexAttribs.end()) { pglVertexAttribPointerARB(it->second, size, type, normalized, stride, pointer); } } virtual void VertexAttribIPointer(attrib_id_t id, GLint size, GLenum type, GLsizei stride, const void* pointer) { std::map::iterator it = m_VertexAttribs.find(id); if (it != m_VertexAttribs.end()) { #if CONFIG2_GLES debug_warn(L"glVertexAttribIPointer not supported on GLES"); #else pglVertexAttribIPointerEXT(it->second, size, type, stride, pointer); #endif } } private: VfsPath m_VertexFile; VfsPath m_FragmentFile; CShaderDefines m_Defines; std::map m_VertexAttribs; GLhandleARB m_Program; GLhandleARB m_VertexShader; GLhandleARB m_FragmentShader; std::map > m_Uniforms; std::map > m_Samplers; // texture target & unit chosen for each uniform sampler }; ////////////////////////////////////////////////////////////////////////// CShaderProgram::CShaderProgram(int streamflags) : m_IsValid(false), m_StreamFlags(streamflags), m_ValidStreams(0) { } #if CONFIG2_GLES /*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& UNUSED(defines), const std::map& UNUSED(vertexIndexes), const std::map& UNUSED(fragmentIndexes), int UNUSED(streamflags)) { LOGERROR("CShaderProgram::ConstructARB: '%s'+'%s': ARB shaders not supported on this device", vertexFile.string8(), fragmentFile.string8()); return NULL; } #else /*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags) { return new CShaderProgramARB(vertexFile, fragmentFile, defines, vertexIndexes, fragmentIndexes, streamflags); } #endif /*static*/ CShaderProgram* CShaderProgram::ConstructGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) { return new CShaderProgramGLSL(vertexFile, fragmentFile, defines, vertexAttribs, streamflags); } bool CShaderProgram::IsValid() const { return m_IsValid; } int CShaderProgram::GetStreamFlags() const { return m_StreamFlags; } void CShaderProgram::BindTexture(texture_id_t id, CTexturePtr tex) { BindTexture(id, tex->GetHandle()); } void CShaderProgram::Uniform(Binding id, int v) { Uniform(id, (float)v, (float)v, (float)v, (float)v); } void CShaderProgram::Uniform(Binding id, float v) { Uniform(id, v, v, v, v); } void CShaderProgram::Uniform(Binding id, float v0, float v1) { Uniform(id, v0, v1, 0.0f, 0.0f); } void CShaderProgram::Uniform(Binding id, const CVector3D& v) { Uniform(id, v.X, v.Y, v.Z, 0.0f); } void CShaderProgram::Uniform(Binding id, const CColor& v) { Uniform(id, v.r, v.g, v.b, v.a); } void CShaderProgram::Uniform(uniform_id_t id, int v) { Uniform(GetUniformBinding(id), (float)v, (float)v, (float)v, (float)v); } void CShaderProgram::Uniform(uniform_id_t id, float v) { Uniform(GetUniformBinding(id), v, v, v, v); } void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1) { Uniform(GetUniformBinding(id), v0, v1, 0.0f, 0.0f); } void CShaderProgram::Uniform(uniform_id_t id, const CVector3D& v) { Uniform(GetUniformBinding(id), v.X, v.Y, v.Z, 0.0f); } void CShaderProgram::Uniform(uniform_id_t id, const CColor& v) { Uniform(GetUniformBinding(id), v.r, v.g, v.b, v.a); } void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1, float v2, float v3) { Uniform(GetUniformBinding(id), v0, v1, v2, v3); } void CShaderProgram::Uniform(uniform_id_t id, const CMatrix3D& v) { Uniform(GetUniformBinding(id), v); } void CShaderProgram::Uniform(uniform_id_t id, size_t count, const CMatrix3D* v) { Uniform(GetUniformBinding(id), count, v); } // These should all be overridden by CShaderProgramGLSL, and not used // if a non-GLSL shader was loaded instead: void CShaderProgram::VertexAttribPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type), GLboolean UNUSED(normalized), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("Shader type doesn't support VertexAttribPointer"); } void CShaderProgram::VertexAttribIPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("Shader type doesn't support VertexAttribIPointer"); } #if CONFIG2_GLES // These should all be overridden by CShaderProgramGLSL // (GLES doesn't support any other types of shader program): void CShaderProgram::VertexPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::VertexPointer should be overridden"); } void CShaderProgram::NormalPointer(GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::NormalPointer should be overridden"); } void CShaderProgram::ColorPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::ColorPointer should be overridden"); } void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::TexCoordPointer should be overridden"); } #else // These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders // both use the fixed-function vertex attribute pointers so we'll share their // definitions here: void CShaderProgram::VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { glVertexPointer(size, type, stride, pointer); m_ValidStreams |= STREAM_POS; } void CShaderProgram::NormalPointer(GLenum type, GLsizei stride, const void* pointer) { glNormalPointer(type, stride, pointer); m_ValidStreams |= STREAM_NORMAL; } void CShaderProgram::ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { glColorPointer(size, type, stride, pointer); m_ValidStreams |= STREAM_COLOR; } void CShaderProgram::TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) { pglClientActiveTextureARB(texture); glTexCoordPointer(size, type, stride, pointer); pglClientActiveTextureARB(GL_TEXTURE0); m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0); } void CShaderProgram::BindClientStates() { ENSURE(m_StreamFlags == (m_StreamFlags & (STREAM_POS|STREAM_NORMAL|STREAM_COLOR|STREAM_UV0|STREAM_UV1))); // Enable all the desired client states for non-GLSL rendering if (m_StreamFlags & STREAM_POS) glEnableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & STREAM_NORMAL) glEnableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & STREAM_COLOR) glEnableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & STREAM_UV0) { pglClientActiveTextureARB(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & STREAM_UV1) { pglClientActiveTextureARB(GL_TEXTURE1); glEnableClientState(GL_TEXTURE_COORD_ARRAY); pglClientActiveTextureARB(GL_TEXTURE0); } // Rendering code must subsequently call VertexPointer etc for all of the streams // that were activated in this function, else AssertPointersBound will complain // that some arrays were unspecified m_ValidStreams = 0; } void CShaderProgram::UnbindClientStates() { if (m_StreamFlags & STREAM_POS) glDisableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & STREAM_NORMAL) glDisableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & STREAM_COLOR) glDisableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & STREAM_UV0) { pglClientActiveTextureARB(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & STREAM_UV1) { pglClientActiveTextureARB(GL_TEXTURE1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); pglClientActiveTextureARB(GL_TEXTURE0); } } #endif // !CONFIG2_GLES void CShaderProgram::AssertPointersBound() { ENSURE((m_StreamFlags & ~m_ValidStreams) == 0); } Index: ps/trunk/source/graphics/ShaderTechnique.h =================================================================== --- ps/trunk/source/graphics/ShaderTechnique.h (revision 24351) +++ ps/trunk/source/graphics/ShaderTechnique.h (revision 24352) @@ -1,114 +1,116 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_SHADERTECHNIQUE #define INCLUDED_SHADERTECHNIQUE #include "graphics/ShaderProgramPtr.h" #include "graphics/ShaderTechniquePtr.h" #include "lib/ogl.h" +#include + /** * Implements a render pass consisting of various GL state changes and a shader, * used by CShaderTechnique. */ class CShaderPass { public: CShaderPass(); /** * Set the shader program used for rendering with this pass. */ void SetShader(const CShaderProgramPtr& shader) { m_Shader = shader; } // Add various bits of GL state to the pass: void AlphaFunc(GLenum func, GLclampf ref); void BlendFunc(GLenum src, GLenum dst); void ColorMask(GLboolean r, GLboolean g, GLboolean b, GLboolean a); void DepthMask(GLboolean mask); void DepthFunc(GLenum func); /** * Set up all the GL state that was previously specified on this pass. */ void Bind(); /** * Reset the GL state to the default. */ void Unbind(); const CShaderProgramPtr& GetShader() const { return m_Shader; } private: CShaderProgramPtr m_Shader; bool m_HasAlpha; GLenum m_AlphaFunc; GLclampf m_AlphaRef; bool m_HasBlend; GLenum m_BlendSrc; GLenum m_BlendDst; bool m_HasColorMask; GLboolean m_ColorMaskR; GLboolean m_ColorMaskG; GLboolean m_ColorMaskB; GLboolean m_ColorMaskA; bool m_HasDepthMask; GLboolean m_DepthMask; bool m_HasDepthFunc; GLenum m_DepthFunc; }; /** * Implements a render technique consisting of a sequence of passes. * CShaderManager loads these from shader effect XML files. */ class CShaderTechnique { public: CShaderTechnique(); void AddPass(const CShaderPass& pass); int GetNumPasses() const; void BeginPass(int pass = 0); void EndPass(int pass = 0); const CShaderProgramPtr& GetShader(int pass = 0) const; /** * Whether this technique uses alpha blending that requires objects to be * drawn from furthest to nearest. */ bool GetSortByDistance() const; void SetSortByDistance(bool enable); void Reset(); private: std::vector m_Passes; bool m_SortByDistance; }; #endif // INCLUDED_SHADERTECHNIQUE Index: ps/trunk/source/graphics/TerrainTextureEntry.cpp =================================================================== --- ps/trunk/source/graphics/TerrainTextureEntry.cpp (revision 24351) +++ ps/trunk/source/graphics/TerrainTextureEntry.cpp (revision 24352) @@ -1,365 +1,366 @@ /* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "TerrainTextureEntry.h" #include "lib/utf8.h" #include "lib/ogl.h" #include "lib/allocators/shared_ptr.h" +#include "lib/file/io/io.h" #include "lib/res/graphics/ogl_tex.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/XML/Xeromyces.h" #include "graphics/MaterialManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerrainProperties.h" #include "graphics/Texture.h" #include "renderer/Renderer.h" #include CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr properties, const VfsPath& path): m_pProperties(properties), m_BaseColor(0), m_BaseColorValid(false) { ENSURE(properties); CXeromyces XeroFile; if (XeroFile.Load(g_VFS, path, "terrain_texture") != PSRETURN_OK) { LOGERROR("Terrain xml not found (%s)", path.string8()); return; } #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(tag); EL(terrain); EL(texture); EL(textures); EL(material); EL(props); EL(alphamap); AT(file); AT(name); #undef AT #undef EL XMBElement root = XeroFile.GetRoot(); if (root.GetNodeName() != el_terrain) { LOGERROR("Invalid terrain format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName()).c_str()); return; } std::vector > samplers; VfsPath alphamap("standard"); m_Tag = utf8_from_wstring(path.Basename().string()); XERO_ITER_EL(root, child) { int child_name = child.GetNodeName(); if (child_name == el_textures) { XERO_ITER_EL(child, textures_element) { ENSURE(textures_element.GetNodeName() == el_texture); CStr name; VfsPath terrainTexturePath; XERO_ITER_ATTR(textures_element, relativePath) { if (relativePath.Name == at_file) terrainTexturePath = VfsPath("art/textures/terrain") / relativePath.Value.FromUTF8(); else if (relativePath.Name == at_name) name = relativePath.Value; } samplers.emplace_back(name, terrainTexturePath); } } else if (child_name == el_material) { VfsPath mat = VfsPath("art/materials") / child.GetText().FromUTF8(); if (CRenderer::IsInitialised()) m_Material = g_Renderer.GetMaterialManager().LoadMaterial(mat); } else if (child_name == el_alphamap) { alphamap = child.GetText().FromUTF8(); } else if (child_name == el_props) { CTerrainPropertiesPtr ret (new CTerrainProperties(properties)); ret->LoadXml(child, &XeroFile, path); if (ret) m_pProperties = ret; } else if (child_name == el_tag) { m_Tag = child.GetText(); } } for (size_t i = 0; i < samplers.size(); ++i) { CTextureProperties texture(samplers[i].second); texture.SetWrap(GL_REPEAT); // TODO: anisotropy should probably be user-configurable, but we want it to be // at least 2 for terrain else the ground looks very blurry when you tilt the // camera upwards texture.SetMaxAnisotropy(2.0f); if (CRenderer::IsInitialised()) { CTexturePtr texptr = g_Renderer.GetTextureManager().CreateTexture(texture); m_Material.AddSampler(CMaterial::TextureSampler(samplers[i].first, texptr)); } } if (CRenderer::IsInitialised()) LoadAlphaMaps(alphamap); float texAngle = 0.f; float texSize = 1.f; if (m_pProperties) { m_Groups = m_pProperties->GetGroups(); texAngle = m_pProperties->GetTextureAngle(); texSize = m_pProperties->GetTextureSize(); } m_TextureMatrix.SetZero(); m_TextureMatrix._11 = cosf(texAngle) / texSize; m_TextureMatrix._13 = -sinf(texAngle) / texSize; m_TextureMatrix._21 = -sinf(texAngle) / texSize; m_TextureMatrix._23 = -cosf(texAngle) / texSize; m_TextureMatrix._44 = 1.f; GroupVector::iterator it=m_Groups.begin(); for (;it!=m_Groups.end();++it) (*it)->AddTerrain(this); } CTerrainTextureEntry::~CTerrainTextureEntry() { for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it) (*it)->RemoveTerrain(this); } // BuildBaseColor: calculate the root color of the texture, used for coloring minimap, and store // in m_BaseColor member void CTerrainTextureEntry::BuildBaseColor() { // Use the explicit properties value if possible if (m_pProperties && m_pProperties->HasBaseColor()) { m_BaseColor=m_pProperties->GetBaseColor(); m_BaseColorValid = true; return; } // Use the texture color if available if (GetTexture()->TryLoad()) { m_BaseColor = GetTexture()->GetBaseColor(); m_BaseColorValid = true; } } const float* CTerrainTextureEntry::GetTextureMatrix() const { return &m_TextureMatrix._11; } // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture void CTerrainTextureEntry::LoadAlphaMaps(VfsPath &amtype) { std::wstring key = L"(alpha map composite" + amtype.string() + L")"; CTerrainTextureManager::TerrainAlphaMap::iterator it = g_TexMan.m_TerrainAlphas.find(amtype); if (it != g_TexMan.m_TerrainAlphas.end()) { m_TerrainAlpha = it; return; } g_TexMan.m_TerrainAlphas[amtype] = TerrainAlpha(); it = g_TexMan.m_TerrainAlphas.find(amtype); TerrainAlpha &result = it->second; // // load all textures and store Handle in array // Handle textures[NUM_ALPHA_MAPS] = {0}; VfsPath path(L"art/textures/terrain/alphamaps"); path = path / amtype; const wchar_t* fnames[NUM_ALPHA_MAPS] = { L"blendcircle.png", L"blendlshape.png", L"blendedge.png", L"blendedgecorner.png", L"blendedgetwocorners.png", L"blendfourcorners.png", L"blendtwooppositecorners.png", L"blendlshapecorner.png", L"blendtwocorners.png", L"blendcorner.png", L"blendtwoedges.png", L"blendthreecorners.png", L"blendushape.png", L"blendbad.png" }; size_t base = 0; // texture width/height (see below) // for convenience, we require all alpha maps to be of the same BPP // (avoids another ogl_tex_get_size call, and doesn't hurt) size_t bpp = 0; for(size_t i=0;i data; AllocateAligned(data, total_w*total_h, maxSectorSize); // for each tile on row for (size_t i = 0; i < NUM_ALPHA_MAPS; i++) { // get src of copy u8* src = 0; DISCARD ogl_tex_get_data(textures[i], &src); size_t srcstep = bpp/8; // get destination of copy u8* dst = data.get() + (i*tile_w); // for each row of image for (size_t j = 0; j < base; j++) { // duplicate first pixel *dst++ = *src; *dst++ = *src; // copy a row for (size_t k = 0; k < base; k++) { *dst++ = *src; src += srcstep; } // duplicate last pixel *dst++ = *(src-srcstep); *dst++ = *(src-srcstep); // advance write pointer for next row dst += total_w-tile_w; } result.m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w); result.m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w); result.m_AlphaMapCoords[i].v0 = 0.0f; result.m_AlphaMapCoords[i].v1 = 1.0f; } for (size_t i = 0; i < NUM_ALPHA_MAPS; i++) DISCARD ogl_tex_free(textures[i]); // upload the composite texture Tex t; DISCARD t.wrap(total_w, total_h, 8, TEX_GREY, data, 0); // uncomment the following to save a png of the generated texture // in the public/ directory, for debugging /*VfsPath filename("blendtex.png"); DynArray da; RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da)); // write to disk //Status ret = INFO::OK; { shared_ptr file = DummySharedPtr(da.base); const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos); if(bytes_written > 0) ENSURE(bytes_written == (ssize_t)da.pos); //else // ret = (Status)bytes_written; } DISCARD da_free(&da);*/ Handle hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); DISCARD ogl_tex_set_filter(hCompositeAlphaMap, GL_LINEAR); DISCARD ogl_tex_set_wrap (hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); ogl_tex_upload(hCompositeAlphaMap, GL_ALPHA, 0, 0); result.m_hCompositeAlphaMap = hCompositeAlphaMap; m_TerrainAlpha = it; } Index: ps/trunk/source/graphics/TextRenderer.cpp =================================================================== --- ps/trunk/source/graphics/TextRenderer.cpp (revision 24351) +++ ps/trunk/source/graphics/TextRenderer.cpp (revision 24352) @@ -1,323 +1,325 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "TextRenderer.h" #include "graphics/Font.h" #include "graphics/FontManager.h" #include "graphics/ShaderProgram.h" #include "lib/ogl.h" #include "ps/CStrIntern.h" #include "ps/GameSetup/Config.h" #include "renderer/Renderer.h" +#include + CTextRenderer::CTextRenderer(const CShaderProgramPtr& shader) : m_Shader(shader) { ResetTransform(); Color(CColor(1.0f, 1.0f, 1.0f, 1.0f)); Font(str_sans_10); } void CTextRenderer::ResetTransform() { float xres = g_xres / g_GuiScale; float yres = g_yres / g_GuiScale; m_Transform.SetIdentity(); m_Transform.Scale(1.0f, -1.f, 1.0f); m_Transform.Translate(0.0f, yres, -1000.0f); CMatrix3D proj; proj.SetOrtho(0.f, xres, 0.f, yres, -1.f, 1000.f); m_Transform = proj * m_Transform; m_Dirty = true; } CMatrix3D CTextRenderer::GetTransform() { return m_Transform; } void CTextRenderer::SetTransform(const CMatrix3D& transform) { m_Transform = transform; m_Dirty = true; } void CTextRenderer::Translate(float x, float y, float z) { CMatrix3D m; m.SetTranslation(x, y, z); m_Transform = m_Transform * m; m_Dirty = true; } void CTextRenderer::SetClippingRect(const CRect& rect) { m_Clipping = rect; } void CTextRenderer::Color(const CColor& color) { if (m_Color != color) { m_Color = color; m_Dirty = true; } } void CTextRenderer::Color(float r, float g, float b, float a) { Color(CColor(r, g, b, a)); } void CTextRenderer::Font(CStrIntern font) { if (font != m_FontName) { m_FontName = font; m_Font = g_Renderer.GetFontManager().LoadFont(font); m_Dirty = true; } } void CTextRenderer::PrintfAdvance(const wchar_t* fmt, ...) { wchar_t buf[1024] = {0}; va_list args; va_start(args, fmt); int ret = vswprintf(buf, ARRAY_SIZE(buf)-1, fmt, args); va_end(args); if (ret < 0) debug_printf("CTextRenderer::Printf vswprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno); PutAdvance(buf); } void CTextRenderer::PrintfAt(float x, float y, const wchar_t* fmt, ...) { wchar_t buf[1024] = {0}; va_list args; va_start(args, fmt); int ret = vswprintf(buf, ARRAY_SIZE(buf)-1, fmt, args); va_end(args); if (ret < 0) debug_printf("CTextRenderer::PrintfAt vswprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno); Put(x, y, buf); } void CTextRenderer::PutAdvance(const wchar_t* buf) { Put(0.0f, 0.0f, buf); int w, h; m_Font->CalculateStringSize(buf, w, h); Translate((float)w, 0.0f, 0.0f); } void CTextRenderer::Put(float x, float y, const wchar_t* buf) { if (buf[0] == 0) return; // empty string; don't bother storing PutString(x, y, new std::wstring(buf), true); } void CTextRenderer::Put(float x, float y, const char* buf) { if (buf[0] == 0) return; // empty string; don't bother storing PutString(x, y, new std::wstring(wstring_from_utf8(buf)), true); } void CTextRenderer::Put(float x, float y, const std::wstring* buf) { if (buf->empty()) return; // empty string; don't bother storing PutString(x, y, buf, false); } void CTextRenderer::PutString(float x, float y, const std::wstring* buf, bool owned) { if (!m_Font) return; // invalid font; can't render if (m_Clipping != CRect()) { float x0, y0, x1, y1; m_Font->GetGlyphBounds(x0, y0, x1, y1); if (y + y1 < m_Clipping.top) return; if (y + y0 > m_Clipping.bottom) return; } // If any state has changed since the last batch, start a new batch if (m_Dirty) { SBatch batch; batch.chars = 0; batch.transform = m_Transform; batch.color = m_Color; batch.font = m_Font; m_Batches.push_back(batch); m_Dirty = false; } // Push a new run onto the latest batch SBatchRun run; run.x = x; run.y = y; m_Batches.back().runs.push_back(run); m_Batches.back().runs.back().text = buf; m_Batches.back().runs.back().owned = owned; m_Batches.back().chars += buf->size(); } struct t2f_v2i { t2f_v2i() : u(0), v(0), x(0), y(0) { } float u, v; i16 x, y; }; struct SBatchCompare { bool operator()(const CTextRenderer::SBatch& a, const CTextRenderer::SBatch& b) { if (a.font < b.font) return true; if (b.font < a.font) return false; // TODO: is it worth sorting by color/transform too? return false; } }; void CTextRenderer::Render() { std::vector indexes; std::vector vertexes; // Try to merge non-consecutive batches that share the same font/color/transform: // sort the batch list by font, then merge the runs of adjacent compatible batches m_Batches.sort(SBatchCompare()); for (std::list::iterator it = m_Batches.begin(); it != m_Batches.end(); ) { std::list::iterator next = it; ++next; if (next != m_Batches.end() && it->font == next->font && it->color == next->color && it->transform == next->transform) { it->chars += next->chars; it->runs.splice(it->runs.end(), next->runs); m_Batches.erase(next); } else ++it; } for (std::list::iterator it = m_Batches.begin(); it != m_Batches.end(); ++it) { SBatch& batch = *it; const CFont::GlyphMap& glyphs = batch.font->GetGlyphs(); m_Shader->BindTexture(str_tex, batch.font->GetTexture()); m_Shader->Uniform(str_transform, batch.transform); // ALPHA-only textures will have .rgb sampled as 0, so we need to // replace it with white (but not affect RGBA textures) if (batch.font->HasRGB()) m_Shader->Uniform(str_colorAdd, CColor(0.0f, 0.0f, 0.0f, 0.0f)); else m_Shader->Uniform(str_colorAdd, CColor(1.0f, 1.0f, 1.0f, 0.0f)); m_Shader->Uniform(str_colorMul, batch.color); vertexes.resize(batch.chars*4); indexes.resize(batch.chars*6); size_t idx = 0; for (std::list::iterator runit = batch.runs.begin(); runit != batch.runs.end(); ++runit) { SBatchRun& run = *runit; i16 x = run.x; i16 y = run.y; for (size_t i = 0; i < run.text->size(); ++i) { const CFont::GlyphData* g = glyphs.get((*run.text)[i]); if (!g) g = glyphs.get(0xFFFD); // Use the missing glyph symbol if (!g) // Missing the missing glyph symbol - give up continue; vertexes[idx*4].u = g->u1; vertexes[idx*4].v = g->v0; vertexes[idx*4].x = g->x1 + x; vertexes[idx*4].y = g->y0 + y; vertexes[idx*4+1].u = g->u0; vertexes[idx*4+1].v = g->v0; vertexes[idx*4+1].x = g->x0 + x; vertexes[idx*4+1].y = g->y0 + y; vertexes[idx*4+2].u = g->u0; vertexes[idx*4+2].v = g->v1; vertexes[idx*4+2].x = g->x0 + x; vertexes[idx*4+2].y = g->y1 + y; vertexes[idx*4+3].u = g->u1; vertexes[idx*4+3].v = g->v1; vertexes[idx*4+3].x = g->x1 + x; vertexes[idx*4+3].y = g->y1 + y; indexes[idx*6+0] = static_cast(idx*4+0); indexes[idx*6+1] = static_cast(idx*4+1); indexes[idx*6+2] = static_cast(idx*4+2); indexes[idx*6+3] = static_cast(idx*4+2); indexes[idx*6+4] = static_cast(idx*4+3); indexes[idx*6+5] = static_cast(idx*4+0); x += g->xadvance; idx++; } } m_Shader->VertexPointer(2, GL_SHORT, sizeof(t2f_v2i), &vertexes[0].x); m_Shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, sizeof(t2f_v2i), &vertexes[0].u); glDrawElements(GL_TRIANGLES, indexes.size(), GL_UNSIGNED_SHORT, &indexes[0]); } m_Batches.clear(); } Index: ps/trunk/source/graphics/TextRenderer.h =================================================================== --- ps/trunk/source/graphics/TextRenderer.h (revision 24351) +++ ps/trunk/source/graphics/TextRenderer.h (revision 24352) @@ -1,179 +1,181 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_TEXTRENDERER #define INCLUDED_TEXTRENDERER #include "graphics/Color.h" #include "graphics/ShaderProgramPtr.h" #include "maths/Matrix3D.h" #include "ps/CStrIntern.h" #include "ps/Shapes.h" +#include + class CFont; class CTextRenderer { public: CTextRenderer(const CShaderProgramPtr& shader); /** * Reset the text transform to the default, with (0,0) in the top-left of the screen. */ void ResetTransform(); CMatrix3D GetTransform(); void SetTransform(const CMatrix3D& transform); void Translate(float x, float y, float z); /** * Set clipping rectangle, in pre-transform coordinates (i.e. text is clipped against * this rect based purely on the x,y values passed into Put()). Text fully outside the * clipping rectangle may not be rendered. Should be used in conjunction with glScissor * for precise clipping - this is just an optimisation. */ void SetClippingRect(const CRect& rect); /** * Set the color for subsequent print calls. */ void Color(const CColor& color); /** * Set the color for subsequent print calls. */ void Color(float r, float g, float b, float a = 1.0); /** * Set the font for subsequent print calls. */ void Font(CStrIntern font); /** * Print formatted text at (0,0) under the current transform, * and advance the transform by the width of the text. */ void PrintfAdvance(const wchar_t* fmt, ...); /** * Print formatted text at (x,y) under the current transform. * Does not alter the current transform. */ void PrintfAt(float x, float y, const wchar_t* fmt, ...); /** * Print text at (0,0) under the current transform, * and advance the transform by the width of the text. */ void PutAdvance(const wchar_t* buf); /** * Print text at (x,y) under the current transform. * Does not alter the current transform. */ void Put(float x, float y, const wchar_t* buf); /** * Print text at (x,y) under the current transform. * Does not alter the current transform. * @p buf must be a UTF-8 string. */ void Put(float x, float y, const char* buf); /** * Print text at (x,y) under the current transform. * Does not alter the current transform. * @p buf must remain valid until Render() is called. * (This should be used to minimise memory copies when possible.) */ void Put(float x, float y, const std::wstring* buf); /** * Render all of the previously printed text calls. */ void Render(); private: friend struct SBatchCompare; /** * A string (optionally owned by this object, or else pointing to an * externally-owned string) with a position. */ struct SBatchRun { private: SBatchRun& operator=(const SBatchRun&); public: SBatchRun() : text(NULL), owned(false) { } SBatchRun(const SBatchRun& str) : x(str.x), y(str.y), owned(str.owned) { if (owned) text = new std::wstring(*str.text); else text = str.text; } ~SBatchRun() { if (owned) delete text; } float x, y; const std::wstring* text; bool owned; }; /** * A list of SBatchRuns, with a single font/color/transform, * to be rendered in a single GL call. */ struct SBatch { size_t chars; // sum of runs[i].text->size() CMatrix3D transform; CColor color; shared_ptr font; std::list runs; }; void PutString(float x, float y, const std::wstring* buf, bool owned); CShaderProgramPtr m_Shader; CMatrix3D m_Transform; CRect m_Clipping; CColor m_Color; CStrIntern m_FontName; shared_ptr m_Font; bool m_Dirty; std::list m_Batches; }; #endif // INCLUDED_TEXTRENDERER Index: ps/trunk/source/graphics/TextureManager.cpp =================================================================== --- ps/trunk/source/graphics/TextureManager.cpp (revision 24351) +++ ps/trunk/source/graphics/TextureManager.cpp (revision 24352) @@ -1,676 +1,678 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "TextureManager.h" #include "graphics/TextureConverter.h" #include "lib/allocators/shared_ptr.h" #include "lib/file/vfs/vfs_tree.h" #include "lib/hash.h" #include "lib/res/graphics/ogl_tex.h" #include "lib/res/h_mgr.h" #include "lib/timer.h" #include "maths/MD5.h" #include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" +#include + #include #include #include struct TPhash { std::size_t operator()(CTextureProperties const& a) const { std::size_t seed = 0; hash_combine(seed, m_PathHash(a.m_Path)); hash_combine(seed, a.m_Filter); hash_combine(seed, a.m_WrapS); hash_combine(seed, a.m_WrapT); hash_combine(seed, a.m_Aniso); hash_combine(seed, a.m_Format); return seed; } std::size_t operator()(CTexturePtr const& a) const { return (*this)(a->m_Properties); } private: std::hash m_PathHash; }; struct TPequal_to { bool operator()(CTextureProperties const& a, CTextureProperties const& b) const { return a.m_Path == b.m_Path && a.m_Filter == b.m_Filter && a.m_WrapS == b.m_WrapS && a.m_WrapT == b.m_WrapT && a.m_Aniso == b.m_Aniso && a.m_Format == b.m_Format; } bool operator()(CTexturePtr const& a, CTexturePtr const& b) const { return (*this)(a->m_Properties, b->m_Properties); } }; class CTextureManagerImpl { friend class CTexture; public: CTextureManagerImpl(PIVFS vfs, bool highQuality, bool disableGL) : m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_DisableGL(disableGL), m_TextureConverter(vfs, highQuality), m_DefaultHandle(0), m_ErrorHandle(0) { // Initialise some textures that will always be available, // without needing to load any files // Default placeholder texture (grey) if (!m_DisableGL) { // Construct 1x1 24-bit texture shared_ptr data(new u8[3], ArrayDeleter()); data.get()[0] = 64; data.get()[1] = 64; data.get()[2] = 64; Tex t; DISCARD t.wrap(1, 1, 24, 0, data, 0); m_DefaultHandle = ogl_tex_wrap(&t, m_VFS, L"(default texture)"); DISCARD ogl_tex_set_filter(m_DefaultHandle, GL_LINEAR); if (!m_DisableGL) DISCARD ogl_tex_upload(m_DefaultHandle); } // Error texture (magenta) if (!m_DisableGL) { // Construct 1x1 24-bit texture shared_ptr data(new u8[3], ArrayDeleter()); data.get()[0] = 255; data.get()[1] = 0; data.get()[2] = 255; Tex t; DISCARD t.wrap(1, 1, 24, 0, data, 0); m_ErrorHandle = ogl_tex_wrap(&t, m_VFS, L"(error texture)"); DISCARD ogl_tex_set_filter(m_ErrorHandle, GL_LINEAR); if (!m_DisableGL) DISCARD ogl_tex_upload(m_ErrorHandle); // Construct a CTexture to return to callers who want an error texture CTextureProperties props(L"(error texture)"); m_ErrorTexture = CTexturePtr(new CTexture(m_ErrorHandle, props, this)); m_ErrorTexture->m_State = CTexture::LOADED; m_ErrorTexture->m_Self = m_ErrorTexture; } // Allow hotloading of textures RegisterFileReloadFunc(ReloadChangedFileCB, this); } ~CTextureManagerImpl() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); DISCARD ogl_tex_free(m_DefaultHandle); DISCARD ogl_tex_free(m_ErrorHandle); } CTexturePtr GetErrorTexture() { return m_ErrorTexture; } /** * See CTextureManager::CreateTexture */ CTexturePtr CreateTexture(const CTextureProperties& props) { // Construct a new default texture with the given properties to use as the search key CTexturePtr texture(new CTexture(m_DefaultHandle, props, this)); // Try to find an existing texture with the given properties TextureCache::iterator it = m_TextureCache.find(texture); if (it != m_TextureCache.end()) return *it; // Can't find an existing texture - finish setting up this new texture texture->m_Self = texture; m_TextureCache.insert(texture); m_HotloadFiles[props.m_Path].insert(texture); return texture; } /** * Load the given file into the texture object and upload it to OpenGL. * Assumes the file already exists. */ void LoadTexture(const CTexturePtr& texture, const VfsPath& path) { if (m_DisableGL) return; PROFILE2("load texture"); PROFILE2_ATTR("name: %ls", path.string().c_str()); Handle h = ogl_tex_load(m_VFS, path, RES_UNIQUE); if (h <= 0) { LOGERROR("Texture failed to load; \"%s\"", texture->m_Properties.m_Path.string8()); // Replace with error texture to make it obvious texture->SetHandle(m_ErrorHandle); return; } // Get some flags for later use size_t flags = 0; DISCARD ogl_tex_get_format(h, &flags, NULL); // Initialise base color from the texture DISCARD ogl_tex_get_average_color(h, &texture->m_BaseColor); // Set GL upload properties DISCARD ogl_tex_set_wrap(h, texture->m_Properties.m_WrapS, texture->m_Properties.m_WrapT); DISCARD ogl_tex_set_anisotropy(h, texture->m_Properties.m_Aniso); // Prevent ogl_tex automatically generating mipmaps (which is slow and unwanted), // by avoiding mipmapped filters unless the source texture already has mipmaps GLint filter = texture->m_Properties.m_Filter; if (!(flags & TEX_MIPMAPS)) { switch (filter) { case GL_NEAREST_MIPMAP_NEAREST: case GL_NEAREST_MIPMAP_LINEAR: filter = GL_NEAREST; break; case GL_LINEAR_MIPMAP_NEAREST: case GL_LINEAR_MIPMAP_LINEAR: filter = GL_LINEAR; break; } } DISCARD ogl_tex_set_filter(h, filter); // Upload to GL if (!m_DisableGL && ogl_tex_upload(h, texture->m_Properties.m_Format) < 0) { LOGERROR("Texture failed to upload: \"%s\"", texture->m_Properties.m_Path.string8()); ogl_tex_free(h); // Replace with error texture to make it obvious texture->SetHandle(m_ErrorHandle); return; } // Let the texture object take ownership of this handle texture->SetHandle(h, true); } /** * Set up some parameters for the loose cache filename code. */ void PrepareCacheKey(const CTexturePtr& texture, MD5& hash, u32& version) { // Hash the settings, so we won't use an old loose cache file if the // settings have changed CTextureConverter::Settings settings = GetConverterSettings(texture); settings.Hash(hash); // Arbitrary version number - change this if we update the code and // need to invalidate old users' caches version = 1; } /** * Attempts to load a cached version of a texture. * If the texture is loaded (or there was an error), returns true. * Otherwise, returns false to indicate the caller should generate the cached version. */ bool TryLoadingCached(const CTexturePtr& texture) { MD5 hash; u32 version; PrepareCacheKey(texture, hash, version); VfsPath loadPath; Status ret = m_CacheLoader.TryLoadingCached(texture->m_Properties.m_Path, hash, version, loadPath); if (ret == INFO::OK) { // Found a cached texture - load it LoadTexture(texture, loadPath); return true; } else if (ret == INFO::SKIPPED) { // No cached version was found - we'll need to create it return false; } else { ENSURE(ret < 0); // No source file or archive cache was found, so we can't load the // real texture at all - return the error texture instead LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", texture->m_Properties.m_Path.string8()); texture->SetHandle(m_ErrorHandle); return true; } } /** * Initiates an asynchronous conversion process, from the texture's * source file to the corresponding loose cache file. */ void ConvertTexture(const CTexturePtr& texture) { VfsPath sourcePath = texture->m_Properties.m_Path; PROFILE2("convert texture"); PROFILE2_ATTR("name: %ls", sourcePath.string().c_str()); MD5 hash; u32 version; PrepareCacheKey(texture, hash, version); VfsPath looseCachePath = m_CacheLoader.LooseCachePath(sourcePath, hash, version); // LOGWARNING("Converting texture \"%s\"", srcPath.c_str()); CTextureConverter::Settings settings = GetConverterSettings(texture); m_TextureConverter.ConvertTexture(texture, sourcePath, looseCachePath, settings); } bool TextureExists(const VfsPath& path) const { return m_VFS->GetFileInfo(m_CacheLoader.ArchiveCachePath(path), 0) == INFO::OK || m_VFS->GetFileInfo(path, 0) == INFO::OK; } bool GenerateCachedTexture(const VfsPath& sourcePath, VfsPath& archiveCachePath) { archiveCachePath = m_CacheLoader.ArchiveCachePath(sourcePath); CTextureProperties textureProps(sourcePath); CTexturePtr texture = CreateTexture(textureProps); CTextureConverter::Settings settings = GetConverterSettings(texture); if (!m_TextureConverter.ConvertTexture(texture, sourcePath, VfsPath("cache") / archiveCachePath, settings)) return false; while (true) { CTexturePtr textureOut; VfsPath dest; bool ok; if (m_TextureConverter.Poll(textureOut, dest, ok)) return ok; std::this_thread::sleep_for(std::chrono::microseconds(1)); } } bool MakeProgress() { // Process any completed conversion tasks { CTexturePtr texture; VfsPath dest; bool ok; if (m_TextureConverter.Poll(texture, dest, ok)) { if (ok) { LoadTexture(texture, dest); } else { LOGERROR("Texture failed to convert: \"%s\"", texture->m_Properties.m_Path.string8()); texture->SetHandle(m_ErrorHandle); } texture->m_State = CTexture::LOADED; return true; } } // We'll only push new conversion requests if it's not already busy bool converterBusy = m_TextureConverter.IsBusy(); if (!converterBusy) { // Look for all high-priority textures needing conversion. // (Iterating over all textures isn't optimally efficient, but it // doesn't seem to be a problem yet and it's simpler than maintaining // multiple queues.) for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) { if ((*it)->m_State == CTexture::HIGH_NEEDS_CONVERTING) { // Start converting this texture (*it)->m_State = CTexture::HIGH_IS_CONVERTING; ConvertTexture(*it); return true; } } } // Try loading prefetched textures from their cache for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) { if ((*it)->m_State == CTexture::PREFETCH_NEEDS_LOADING) { if (TryLoadingCached(*it)) { (*it)->m_State = CTexture::LOADED; } else { (*it)->m_State = CTexture::PREFETCH_NEEDS_CONVERTING; } return true; } } // If we've got nothing better to do, then start converting prefetched textures. if (!converterBusy) { for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) { if ((*it)->m_State == CTexture::PREFETCH_NEEDS_CONVERTING) { (*it)->m_State = CTexture::PREFETCH_IS_CONVERTING; ConvertTexture(*it); return true; } } } return false; } /** * Compute the conversion settings that apply to a given texture, by combining * the textures.xml files from its directory and all parent directories * (up to the VFS root). */ CTextureConverter::Settings GetConverterSettings(const CTexturePtr& texture) { fs::wpath srcPath = texture->m_Properties.m_Path.string(); std::vector files; VfsPath p; for (fs::wpath::iterator it = srcPath.begin(); it != srcPath.end(); ++it) { VfsPath settingsPath = p / "textures.xml"; m_HotloadFiles[settingsPath].insert(texture); CTextureConverter::SettingsFile* f = GetSettingsFile(settingsPath); if (f) files.push_back(f); p = p / GetWstringFromWpath(*it); } return m_TextureConverter.ComputeSettings(GetWstringFromWpath(srcPath.leaf()), files); } /** * Return the (cached) settings file with the given filename, * or NULL if it doesn't exist. */ CTextureConverter::SettingsFile* GetSettingsFile(const VfsPath& path) { SettingsFilesMap::iterator it = m_SettingsFiles.find(path); if (it != m_SettingsFiles.end()) return it->second.get(); if (m_VFS->GetFileInfo(path, NULL) >= 0) { shared_ptr settings(m_TextureConverter.LoadSettings(path)); m_SettingsFiles.insert(std::make_pair(path, settings)); return settings.get(); } else { m_SettingsFiles.insert(std::make_pair(path, shared_ptr())); return NULL; } } static Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } Status ReloadChangedFile(const VfsPath& path) { // Uncache settings file, if this is one m_SettingsFiles.erase(path); // Find all textures using this file HotloadFilesMap::iterator files = m_HotloadFiles.find(path); if (files != m_HotloadFiles.end()) { // Flag all textures using this file as needing reloading for (std::set >::iterator it = files->second.begin(); it != files->second.end(); ++it) { if (std::shared_ptr texture = it->lock()) { texture->m_State = CTexture::UNLOADED; texture->SetHandle(m_DefaultHandle); } } } return INFO::OK; } size_t GetBytesUploaded() const { size_t size = 0; for (TextureCache::const_iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) size += (*it)->GetUploadedSize(); return size; } private: PIVFS m_VFS; CCacheLoader m_CacheLoader; bool m_DisableGL; CTextureConverter m_TextureConverter; Handle m_DefaultHandle; Handle m_ErrorHandle; CTexturePtr m_ErrorTexture; // Cache of all loaded textures using TextureCache = std::unordered_set; TextureCache m_TextureCache; // TODO: we ought to expire unused textures from the cache eventually // Store the set of textures that need to be reloaded when the given file // (a source file or settings.xml) is modified using HotloadFilesMap = std::unordered_map, std::owner_less > > >; HotloadFilesMap m_HotloadFiles; // Cache for the conversion settings files using SettingsFilesMap = std::unordered_map >; SettingsFilesMap m_SettingsFiles; }; CTexture::CTexture(Handle handle, const CTextureProperties& props, CTextureManagerImpl* textureManager) : m_Handle(handle), m_BaseColor(0), m_State(UNLOADED), m_Properties(props), m_TextureManager(textureManager) { // Add a reference to the handle (it might be shared by multiple CTextures // so we can't take ownership of it) if (m_Handle) h_add_ref(m_Handle); } CTexture::~CTexture() { if (m_Handle) ogl_tex_free(m_Handle); } void CTexture::Bind(size_t unit) { ogl_tex_bind(GetHandle(), unit); } Handle CTexture::GetHandle() { // TODO: TryLoad might call ogl_tex_upload which enables GL_TEXTURE_2D // on texture unit 0, regardless of 'unit', which callers might // not be expecting. Ideally that wouldn't happen. TryLoad(); return m_Handle; } bool CTexture::TryLoad() { // If we haven't started loading, then try loading, and if that fails then request conversion. // If we have already tried prefetch loading, and it failed, bump the conversion request to HIGH priority. if (m_State == UNLOADED || m_State == PREFETCH_NEEDS_LOADING || m_State == PREFETCH_NEEDS_CONVERTING) { if (std::shared_ptr self = m_Self.lock()) { if (m_State != PREFETCH_NEEDS_CONVERTING && m_TextureManager->TryLoadingCached(self)) m_State = LOADED; else m_State = HIGH_NEEDS_CONVERTING; } } return (m_State == LOADED); } void CTexture::Prefetch() { if (m_State == UNLOADED) { if (std::shared_ptr self = m_Self.lock()) { m_State = PREFETCH_NEEDS_LOADING; } } } bool CTexture::IsLoaded() { return (m_State == LOADED); } void CTexture::SetHandle(Handle handle, bool takeOwnership) { if (handle == m_Handle) return; if (!takeOwnership) h_add_ref(handle); ogl_tex_free(m_Handle); m_Handle = handle; } size_t CTexture::GetWidth() const { size_t w = 0; DISCARD ogl_tex_get_size(m_Handle, &w, 0, 0); return w; } size_t CTexture::GetHeight() const { size_t h = 0; DISCARD ogl_tex_get_size(m_Handle, 0, &h, 0); return h; } bool CTexture::HasAlpha() const { size_t flags = 0; DISCARD ogl_tex_get_format(m_Handle, &flags, 0); return (flags & TEX_ALPHA) != 0; } u32 CTexture::GetBaseColor() const { return m_BaseColor; } size_t CTexture::GetUploadedSize() const { size_t size = 0; DISCARD ogl_tex_get_uploaded_size(m_Handle, &size); return size; } // CTextureManager: forward all calls to impl: CTextureManager::CTextureManager(PIVFS vfs, bool highQuality, bool disableGL) : m(new CTextureManagerImpl(vfs, highQuality, disableGL)) { } CTextureManager::~CTextureManager() { delete m; } CTexturePtr CTextureManager::CreateTexture(const CTextureProperties& props) { return m->CreateTexture(props); } bool CTextureManager::TextureExists(const VfsPath& path) const { return m->TextureExists(path); } CTexturePtr CTextureManager::GetErrorTexture() { return m->GetErrorTexture(); } bool CTextureManager::MakeProgress() { return m->MakeProgress(); } bool CTextureManager::GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath) { return m->GenerateCachedTexture(path, outputPath); } size_t CTextureManager::GetBytesUploaded() const { return m->GetBytesUploaded(); } Index: ps/trunk/source/graphics/UnitAnimation.h =================================================================== --- ps/trunk/source/graphics/UnitAnimation.h (revision 24351) +++ ps/trunk/source/graphics/UnitAnimation.h (revision 24352) @@ -1,140 +1,142 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_UNITANIMATION #define INCLUDED_UNITANIMATION #include "ps/CStr.h" #include "simulation2/system/Entity.h" +#include + class CUnit; class CModel; class CSkeletonAnim; class CObjectEntry; /** * Deals with synchronisation issues between raw animation data (CModel, CSkeletonAnim) * and the simulation system (via CUnit), providing a simple fire-and-forget API to play animations. * (This is really just a component of CUnit and could probably be merged back into that class.) */ class CUnitAnimation { NONCOPYABLE(CUnitAnimation); public: /** * Construct for a given unit, defaulting to the "idle" animation. */ CUnitAnimation(entity_id_t ent, CModel* model, CObjectEntry* object); /** * Change the entity ID associated with this animation * (currently used for playing locational sound effects). */ void SetEntityID(entity_id_t ent); /** * Start playing an animation. * The unit's actor defines the available animations, and if more than one is available * then one is picked at random (with a new random choice each loop). * By default, animations start immediately and run at the given speed with no syncing. * Use SetAnimationSync after this to force a specific timing, if it needs to match the * simulation timing. * Alternatively, set @p desync to a non-zero value (e.g. 0.05) to slightly randomise the * offset and speed, so units don't all move in lockstep. * @param name animation's name ("idle", "walk", etc) * @param once if true then the animation freezes on its last frame; otherwise it loops * @param speed fraction of actor-defined speed to play back at (should typically be 1.0) * @param desync maximum fraction of length/speed to randomly adjust timings (or 0.0 for no desyncing) * @param actionSound sound group name to be played at the 'action' point in the animation, or empty string */ void SetAnimationState(const CStr& name, bool once, float speed, float desync, const CStrW& actionSound); /** * Adjust the speed of the current animation, so that Update(repeatTime) will do a * complete animation loop. * @param repeatTime time for complete loop of animation, in msec */ void SetAnimationSyncRepeat(float repeatTime); /** * Adjust the offset of the current animation, so that Update(actionTime) will advance it * to the 'action' point defined in the actor. * This must be called after SetAnimationSyncRepeat sets the speed. * @param actionTime time between now and when the action should occur, in msec */ void SetAnimationSyncOffset(float actionTime); /** * Advance the animation state. * @param time advance time in msec */ void Update(float time); /** * Regenerate internal animation state from the models in the current unit. * This should be called whenever the unit is changed externally, to keep this in sync. */ void ReloadUnit(CModel* model, const CObjectEntry* object); /** * Reload animation so any changes take immediate effect. */ void ReloadAnimation(); private: /** * Picks a new animation ID from our current state */ void UpdateAnimationID(); struct SModelAnimState { CModel* model; CSkeletonAnim* anim; const CObjectEntry* object; float time; bool pastLoadPos; bool pastActionPos; bool pastSoundPos; }; std::vector m_AnimStates; /** * True if all the current AnimStates are static, so Update() doesn't need * to do any work at all */ bool m_AnimStatesAreStatic; void AddModel(CModel* model, const CObjectEntry* object); entity_id_t m_Entity; CModel* m_Model; const CObjectEntry* m_Object; CStr m_State; CStr m_AnimationID = ""; bool m_Looping; float m_OriginalSpeed; float m_Speed; float m_SyncRepeatTime; float m_Desync; CStrW m_ActionSound; }; #endif // INCLUDED_UNITANIMATION Index: ps/trunk/source/graphics/tests/test_Color.h =================================================================== --- ps/trunk/source/graphics/tests/test_Color.h (revision 24351) +++ ps/trunk/source/graphics/tests/test_Color.h (revision 24352) @@ -1,48 +1,50 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "lib/self_test.h" #include "graphics/Color.h" +#include + class TestColor : public CxxTest::TestSuite { public: void setUp() { ColorActivateFastImpl(); } void test_Color4ub() { CheckColor(0, 0, 0, 0x000000); CheckColor(1, 0, 0, 0x0000ff); CheckColor(0, 1, 0, 0x00ff00); CheckColor(0, 0, 1, 0xff0000); CheckColor(1, 1, 1, 0xffffff); } private: void CheckColor(int r, int g, int b, u32 expected) { SColor4ub colorStruct = ConvertRGBColorTo4ub(RGBColor(r,g,b)); u32 actual; memcpy(&actual, &colorStruct, sizeof(u32)); expected |= 0xff000000; // ConvertRGBColorTo4ub sets alpha to opaque TS_ASSERT_EQUALS(expected, actual); } }; Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 24351) +++ ps/trunk/source/gui/CGUI.cpp (revision 24352) @@ -1,1293 +1,1299 @@ /* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "CGUI.h" #include "gui/IGUIScrollBar.h" +#include "gui/ObjectTypes/CGUIDummyObject.h" #include "gui/ObjectTypes/CTooltip.h" #include "gui/Scripting/ScriptFunctions.h" #include "i18n/L10n.h" #include "lib/bits.h" #include "lib/input.h" #include "lib/sysdep/sysdep.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/GameSetup/Config.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "scriptinterface/ScriptContext.h" #include "scriptinterface/ScriptInterface.h" #include #include extern int g_yres; const double SELECT_DBLCLICK_RATE = 0.5; const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion const CStr CGUI::EventNameLoad = "Load"; const CStr CGUI::EventNameTick = "Tick"; const CStr CGUI::EventNamePress = "Press"; const CStr CGUI::EventNameKeyDown = "KeyDown"; const CStr CGUI::EventNameRelease = "Release"; const CStr CGUI::EventNameMouseRightPress = "MouseRightPress"; const CStr CGUI::EventNameMouseLeftPress = "MouseLeftPress"; const CStr CGUI::EventNameMouseWheelDown = "MouseWheelDown"; const CStr CGUI::EventNameMouseWheelUp = "MouseWheelUp"; const CStr CGUI::EventNameMouseLeftDoubleClick = "MouseLeftDoubleClick"; const CStr CGUI::EventNameMouseLeftRelease = "MouseLeftRelease"; const CStr CGUI::EventNameMouseRightDoubleClick = "MouseRightDoubleClick"; const CStr CGUI::EventNameMouseRightRelease = "MouseRightRelease"; CGUI::CGUI(const shared_ptr& context) - : m_BaseObject(*this), + : m_BaseObject(new CGUIDummyObject(*this)), m_FocusedObject(nullptr), m_InternalNameNumber(0), m_MouseButtons(0) { m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", context)); m_ScriptInterface->SetCallbackData(this); GuiScriptingInit(*m_ScriptInterface); m_ScriptInterface->LoadGlobalScripts(); } CGUI::~CGUI() { for (const std::pair& p : m_pAllObjects) delete p.second; for (const std::pair& p : m_Sprites) delete p.second; } InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYPRESS || ev->ev.type == SDL_HOTKEYUP) { const char* hotkey = static_cast(ev->ev.user.data1); const CStr& eventName = ev->ev.type == SDL_HOTKEYPRESS ? EventNamePress : ev->ev.type == SDL_HOTKEYDOWN ? EventNameKeyDown : EventNameRelease; if (m_GlobalHotkeys.find(hotkey) != m_GlobalHotkeys.end() && m_GlobalHotkeys[hotkey].find(eventName) != m_GlobalHotkeys[hotkey].end()) { ret = IN_HANDLED; ScriptRequest rq(m_ScriptInterface); JS::RootedObject globalObj(rq.cx, rq.glob); JS::RootedValue result(rq.cx); if (!JS_CallFunctionValue(rq.cx, globalObj, m_GlobalHotkeys[hotkey][eventName], JS::HandleValueArray::empty(), &result)) ScriptException::CatchPending(rq); } std::map >::iterator it = m_HotkeyObjects.find(hotkey); if (it != m_HotkeyObjects.end()) for (IGUIObject* const& obj : it->second) { if (ev->ev.type == SDL_HOTKEYPRESS) ret = obj->SendEvent(GUIM_PRESSED, EventNamePress); else if (ev->ev.type == SDL_HOTKEYDOWN) ret = obj->SendEvent(GUIM_KEYDOWN, EventNameKeyDown); else ret = obj->SendEvent(GUIM_RELEASED, EventNameRelease); } } else if (ev->ev.type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversions when operating in a // float-based environment. m_MousePos = CPos((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale); SGUIMessage msg(GUIM_MOUSE_MOTION); - m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::HandleMessage, msg); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::HandleMessage, msg); } // Update m_MouseButtons. (BUTTONUP is handled later.) else if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons |= Bit(ev->ev.button.button); break; default: break; } } // Update m_MousePos (for delayed mouse button events) CPos oldMousePos = m_MousePos; if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) { m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale); } // Allow the focused object to pre-empt regular GUI events. if (GetFocusedObject()) ret = GetFocusedObject()->PreemptEvent(ev); // Only one object can be hovered // pNearest will after this point at the hovered object, possibly nullptr IGUIObject* pNearest = FindObjectUnderMouse(); if (ret == IN_PASS) { // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly - m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: // Focus the clicked object (or focus none if nothing clicked on) SetFocusedObject(pNearest); if (pNearest) ret = pNearest->SendMouseEvent(GUIM_MOUSE_PRESS_LEFT, EventNameMouseLeftPress); break; case SDL_BUTTON_RIGHT: if (pNearest) ret = pNearest->SendMouseEvent(GUIM_MOUSE_PRESS_RIGHT, EventNameMouseRightPress); break; default: break; } } else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest) { if (ev->ev.wheel.y < 0) ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_DOWN, EventNameMouseWheelDown); else if (ev->ev.wheel.y > 0) ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_UP, EventNameMouseWheelUp); } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT]; pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendMouseEvent(GUIM_MOUSE_DBLCLICK_LEFT, EventNameMouseLeftDoubleClick); else ret = pNearest->SendMouseEvent(GUIM_MOUSE_RELEASE_LEFT, EventNameMouseLeftRelease); } break; case SDL_BUTTON_RIGHT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT]; pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendMouseEvent(GUIM_MOUSE_DBLCLICK_RIGHT, EventNameMouseRightDoubleClick); else ret = pNearest->SendMouseEvent(GUIM_MOUSE_RELEASE_RIGHT, EventNameMouseRightRelease); } break; } // Reset all states on all visible objects - m_BaseObject.RecurseObject(&IGUIObject::IsHidden, &IGUIObject::ResetStates); + m_BaseObject->RecurseObject(&IGUIObject::IsHidden, &IGUIObject::ResetStates); // Since the hover state will have been reset, we reload it. - m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); } } // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons &= ~Bit(ev->ev.button.button); break; default: break; } } // Restore m_MousePos (for delayed mouse button events) if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) m_MousePos = oldMousePos; // Let GUI items handle keys after everything else, e.g. for input boxes. if (ret == IN_PASS && GetFocusedObject()) { if (ev->ev.type == SDL_KEYUP || ev->ev.type == SDL_KEYDOWN || ev->ev.type == SDL_HOTKEYUP || ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING) ret = GetFocusedObject()->ManuallyHandleKeys(ev); // else will return IN_PASS because we never used the button. } return ret; } void CGUI::TickObjects() { - m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::Tick); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::Tick); SendEventToAll(EventNameTick); m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, *this); } void CGUI::SendEventToAll(const CStr& eventName) { auto it = m_EventIGUIObjects.find(eventName); if (it == m_EventIGUIObjects.end()) return; std::set copy = it->second; for (IGUIObject* pIGUIObject : copy) pIGUIObject->ScriptEvent(eventName); } void CGUI::SendEventToAll(const CStr& eventName, const JS::HandleValueArray& paramData) { auto it = m_EventIGUIObjects.find(eventName); if (it == m_EventIGUIObjects.end()) return; std::set copy = it->second; for (IGUIObject* pIGUIObject : copy) pIGUIObject->ScriptEvent(eventName, paramData); } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); - m_BaseObject.RecurseObject(&IGUIObject::IsHidden, &IGUIObject::Draw); + m_BaseObject->RecurseObject(&IGUIObject::IsHidden, &IGUIObject::Draw); } void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping)) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (!Sprite) return; // TODO: Clipping? Sprite.Draw(*this, Rect, CellID, m_Sprites, Z); } void CGUI::UpdateResolution() { - m_BaseObject.RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); + m_BaseObject->RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); } IGUIObject* CGUI::ConstructObject(const CStr& str) { std::map::iterator it = m_ObjectTypes.find(str); if (it == m_ObjectTypes.end()) return nullptr; return (*it->second)(*this); } bool CGUI::AddObject(IGUIObject& parent, IGUIObject& child) { if (child.m_Name.empty()) { LOGERROR("Can't register an object without name!"); return false; } if (m_pAllObjects.find(child.m_Name) != m_pAllObjects.end()) { LOGERROR("Can't register more than one object of the name %s", child.m_Name.c_str()); return false; } m_pAllObjects[child.m_Name] = &child; parent.AddChild(child); return true; } +IGUIObject* CGUI::GetBaseObject() +{ + return m_BaseObject.get(); +}; + bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.find(Name) != m_pAllObjects.end(); } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return nullptr; return it->second; } IGUIObject* CGUI::FindObjectUnderMouse() { IGUIObject* pNearest = nullptr; - m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::ChooseMouseOverAndClosest, pNearest); + m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::ChooseMouseOverAndClosest, pNearest); return pNearest; } void CGUI::SetFocusedObject(IGUIObject* pObject) { if (pObject == m_FocusedObject) return; if (m_FocusedObject) { SGUIMessage msg(GUIM_LOST_FOCUS); m_FocusedObject->HandleMessage(msg); } m_FocusedObject = pObject; if (m_FocusedObject) { SGUIMessage msg(GUIM_GOT_FOCUS); m_FocusedObject->HandleMessage(msg); } } void CGUI::SetObjectStyle(IGUIObject* pObject, const CStr& styleName) { // If the style is not recognised (or an empty string) then ApplyStyle will // emit an error message. Thus we don't need to handle it here. if (pObject->ApplyStyle(styleName)) pObject->m_Style = styleName; } void CGUI::UnsetObjectStyle(IGUIObject* pObject) { SetObjectStyle(pObject, "default"); } void CGUI::SetObjectHotkey(IGUIObject* pObject, const CStr& hotkeyTag) { if (!hotkeyTag.empty()) m_HotkeyObjects[hotkeyTag].push_back(pObject); } void CGUI::UnsetObjectHotkey(IGUIObject* pObject, const CStr& hotkeyTag) { if (hotkeyTag.empty()) return; std::vector& assignment = m_HotkeyObjects[hotkeyTag]; assignment.erase( std::remove_if( assignment.begin(), assignment.end(), [&pObject](const IGUIObject* hotkeyObject) { return pObject == hotkeyObject; }), assignment.end()); } void CGUI::SetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName, JS::HandleValue function) { ScriptRequest rq(*m_ScriptInterface); if (hotkeyTag.empty()) { ScriptException::Raise(rq, "Cannot assign a function to an empty hotkey identifier!"); return; } // Only support "Press", "Keydown" and "Release" events. if (eventName != EventNamePress && eventName != EventNameKeyDown && eventName != EventNameRelease) { ScriptException::Raise(rq, "Cannot assign a function to an unsupported event!"); return; } if (!function.isObject() || !JS_ObjectIsFunction(&function.toObject())) { ScriptException::Raise(rq, "Cannot assign non-function value to global hotkey '%s'", hotkeyTag.c_str()); return; } UnsetGlobalHotkey(hotkeyTag, eventName); m_GlobalHotkeys[hotkeyTag][eventName].init(rq.cx, function); } void CGUI::UnsetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName) { std::map>::iterator it = m_GlobalHotkeys.find(hotkeyTag); if (it == m_GlobalHotkeys.end()) return; m_GlobalHotkeys[hotkeyTag].erase(eventName); if (m_GlobalHotkeys.count(hotkeyTag) == 0) m_GlobalHotkeys.erase(it); } const SGUIScrollBarStyle* CGUI::GetScrollBarStyle(const CStr& style) const { std::map::const_iterator it = m_ScrollBarStyles.find(style); if (it == m_ScrollBarStyles.end()) return nullptr; return &it->second; } /** * @callgraph */ void CGUI::LoadXmlFile(const VfsPath& Filename, std::unordered_set& Paths) { Paths.insert(Filename); CXeromyces XeroFile; if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK) return; XMBElement node = XeroFile.GetRoot(); CStr root_name(XeroFile.GetElementString(node.GetNodeName())); if (root_name == "objects") Xeromyces_ReadRootObjects(node, &XeroFile, Paths); else if (root_name == "sprites") Xeromyces_ReadRootSprites(node, &XeroFile); else if (root_name == "styles") Xeromyces_ReadRootStyles(node, &XeroFile); else if (root_name == "setup") Xeromyces_ReadRootSetup(node, &XeroFile); else LOGERROR("CGUI::LoadXmlFile encountered an unknown XML root node type: %s", root_name.c_str()); } void CGUI::LoadedXmlFiles() { - m_BaseObject.RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); + m_BaseObject->RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); SGUIMessage msg(GUIM_LOAD); - m_BaseObject.RecurseObject(nullptr, &IGUIObject::HandleMessage, msg); + m_BaseObject->RecurseObject(nullptr, &IGUIObject::HandleMessage, msg); SendEventToAll(EventNameLoad); } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, std::unordered_set& Paths) { int el_script = pFile->GetElementID("script"); std::vector > subst; // Iterate main children // they should all be or