Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce memory usage: do not allocate memory for the transform layer for single layer images #7233

Closed
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
135f98c
Do not allocate memory for 'transform' layer for singleLayer images,
Districh-ru May 26, 2023
6a01c6e
Merge branch 'master' into disable_transform_layer_for_singlelayer_im…
Districh-ru May 26, 2023
4f30b38
Add braces to solve code format tool ambiguity
Districh-ru May 26, 2023
9799155
Fix some Clang-Tidy issues
Districh-ru May 26, 2023
80e8356
Apply IWYU suggestion
Districh-ru May 26, 2023
1bf46fc
Code style
Districh-ru May 26, 2023
fb2b4c6
Fix comments
Districh-ru May 26, 2023
b422d2e
Load more ICNs as single layer
Districh-ru May 27, 2023
784aa29
Merge 'master' branch
Districh-ru May 27, 2023
2415c9a
Rework 'decodeICNSprite()' to load images ignoring the transform laye…
Districh-ru May 27, 2023
60a4b30
Fix some Clang-Tidy issues in agg_image
Districh-ru May 27, 2023
57aac72
Load some non-transparent ICNs as singleLayer images
Districh-ru May 27, 2023
a2e471a
Some optimizations for Earthquake and Armageddon spells and fadeArena…
Districh-ru May 27, 2023
5024a33
Rework DrawLine(), DrawBorder(), CreateDitheringTransition() and Extr…
Districh-ru May 27, 2023
0bf24ff
Fix Well exit button
Districh-ru May 27, 2023
f8dd60a
Address some @ihhub comments
Districh-ru May 28, 2023
5209841
Fix h2d load
Districh-ru May 28, 2023
678ebb3
Revert Image and Sprite constructors
Districh-ru May 28, 2023
df9345e
Revert forced ICN singleLayer load
Districh-ru May 28, 2023
d039729
Fix comments. Add check for need of transform layer after loading ICN.
Districh-ru May 28, 2023
d4ea32c
Fix IWYU issue
Districh-ru May 28, 2023
b7727bb
Merge branch 'master'
Districh-ru May 30, 2023
673d3c9
Update format of 'battle_interface.cpp'
Districh-ru May 30, 2023
ea2844a
Fix display "Data files not found!" - make it as singleLayer image
Districh-ru May 31, 2023
175d48c
Fix Clang-Tidy and IWYU issues
Districh-ru May 31, 2023
391f288
Replace assertions with checks for singleLayer to '_disableTransformL…
Districh-ru Jun 1, 2023
c66994e
Merge branch 'master' into disable_transform_layer_for_singlelayer_im…
Districh-ru Jun 9, 2023
82b5611
Address @ihhub comments and fixed incorrect pixels in some ICNs
Districh-ru Jun 10, 2023
3568b49
Remove non-needed include
Districh-ru Jun 10, 2023
5079a01
Fix transparency for ARMY / MARKET button
Districh-ru Jun 11, 2023
7f7a756
Split _data to _imageData and _transformData to speed-up converting i…
Districh-ru Jun 12, 2023
da8a559
Merge 'master' branch
Districh-ru Jul 1, 2023
49edd97
Fix conflicts after the merge
Districh-ru Jul 1, 2023
fa5c61f
Merge 'master' branch
Districh-ru Jul 5, 2023
cf082e6
Code style fix
Districh-ru Jul 5, 2023
1f361b9
Minor code fixes
Districh-ru Jul 5, 2023
bb0b8fb
Fix main menu ICN load with Russian (Buka) assets; remove 'replaceTra…
Districh-ru Jul 5, 2023
854b95a
ICN decode speed-up (about 1%)
Districh-ru Jul 7, 2023
f92ba5a
Another 1-2% ICN decode speed-up. :)
Districh-ru Jul 7, 2023
67e0b54
Merge branch 'master' into disable_transform_layer_for_singlelayer_im…
Districh-ru Jul 7, 2023
c3606bd
Merge branch 'master' into disable_transform_layer_for_singlelayer_im…
Districh-ru Jul 8, 2023
6fe0c4e
Merge branch 'master' into disable_transform_layer_for_singlelayer_im…
Districh-ru Jul 22, 2023
a08100d
Merge branch 'master' into disable_transform_layer_for_singlelayer_im…
Districh-ru Jul 23, 2023
9bca961
Fix issues after the merge, revert some changes
Districh-ru Jul 23, 2023
5b0c355
Merge branch 'ihhub:master' into disable_transform_layer_for_singlela…
Districh-ru Jul 24, 2023
ba63a3f
Merge branch 'master' into disable_transform_layer_for_singlelayer_im…
Districh-ru Oct 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/engine/h2d_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ namespace fheroes2

bool readImageFromH2D( H2DReader & reader, const std::string & name, Sprite & image )
{
// TODO: Add the transform layer presence flag in H2D images and read H2D single-layer images properly without the transform layer.

// Currently all H2D images are double-layer images so the out 'image' should not be single-layer.
assert( !image.singleLayer() );
ihhub marked this conversation as resolved.
Show resolved Hide resolved

const std::vector<uint8_t> & data = reader.getFile( name );
if ( data.size() < 4 + 4 + 4 + 4 + 1 ) {
// Empty or invalid image.
Expand Down Expand Up @@ -201,7 +206,10 @@ namespace fheroes2

bool writeImageToH2D( H2DWriter & writer, const std::string & name, const Sprite & image )
{
assert( !image.empty() );
// TODO: Add the transform layer presence flag in H2D images and write single-layer images properly without the transform layer.

// Currently all H2D images are double-layer images so the out 'image' should not be single-layer.
assert( !image.empty() && !image.singleLayer() );

StreamBuf stream;
stream.putLE32( static_cast<uint32_t>( image.width() ) );
Expand Down
82 changes: 60 additions & 22 deletions src/engine/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,8 @@ namespace fheroes2
}

Image::Image( Image && image_ ) noexcept
: _data( std::move( image_._data ) )
: _imageData( std::move( image_._imageData ) )
, _transformData( std::move( image_._transformData ) )
{
std::swap( _singleLayer, image_._singleLayer );
std::swap( _width, image_._width );
Expand All @@ -469,26 +470,18 @@ namespace fheroes2
if ( this != &image_ ) {
std::swap( _width, image_._width );
std::swap( _height, image_._height );
std::swap( _data, image_._data );
std::swap( _imageData, image_._imageData );
std::swap( _transformData, image_._transformData );
std::swap( _singleLayer, image_._singleLayer );
}

return *this;
}

uint8_t * Image::image()
{
return _data.get();
}

const uint8_t * Image::image() const
{
return _data.get();
}

void Image::clear()
{
_data.reset();
_imageData.reset();
_transformData.reset();

_width = 0;
_height = 0;
Expand All @@ -499,7 +492,11 @@ namespace fheroes2
if ( !empty() ) {
const size_t totalSize = static_cast<size_t>( _width ) * _height;
memset( image(), value, totalSize );
memset( transform(), static_cast<uint8_t>( 0 ), totalSize );

if ( !_singleLayer ) {
// Set the transform layer not to skip all data.
memset( transform(), static_cast<uint8_t>( 0 ), totalSize );
}
}
}

Expand All @@ -515,27 +512,44 @@ namespace fheroes2
return;
}

const size_t size = static_cast<size_t>( width_ ) * height_;
size_t size = static_cast<size_t>( width_ ) * height_;
_imageData.reset( new uint8_t[size] );

_data.reset( new uint8_t[size * 2] );
if ( !_singleLayer ) {
// For double-layer images we also allocate memory for the second (transform) layer.
_transformData.reset( new uint8_t[size] );
}

_width = width_;
_height = height_;
}

uint8_t * Image::image()
{
return _imageData.get();
}

const uint8_t * Image::image() const
{
return _imageData.get();
}

void Image::reset()
{
if ( !empty() ) {
const size_t totalSize = static_cast<size_t>( _width ) * _height;
memset( image(), static_cast<uint8_t>( 0 ), totalSize );
// Set the transform layer to skip all data.
memset( transform(), static_cast<uint8_t>( 1 ), totalSize );

if ( !_singleLayer ) {
// Set the transform layer to skip all data.
memset( transform(), static_cast<uint8_t>( 1 ), totalSize );
}
}
}

void Image::copy( const Image & image )
{
if ( !image._data ) {
if ( !image._imageData ) {
clear();

return;
Expand All @@ -544,15 +558,36 @@ namespace fheroes2
const size_t size = static_cast<size_t>( image._width ) * image._height;

if ( image._width != _width || image._height != _height ) {
_data.reset( new uint8_t[size * 2] );
_imageData.reset( new uint8_t[size] );

_width = image._width;
_height = image._height;

if ( !image._singleLayer ) {
_transformData.reset( new uint8_t[size] );
}
else if ( !_singleLayer ) {
_transformData.reset();
}

_singleLayer = image._singleLayer;
}
else if ( _singleLayer != image._singleLayer ) {
_singleLayer = image._singleLayer;

if ( _singleLayer ) {
_transformData.reset( new uint8_t[size] );
}
else {
_transformData.reset();
}
}

_singleLayer = image._singleLayer;
memcpy( _imageData.get(), image._imageData.get(), size );

memcpy( _data.get(), image._data.get(), size * 2 );
if ( !_singleLayer ) {
memcpy( _transformData.get(), image._transformData.get(), size );
}
}

Sprite::Sprite( const int32_t width_, const int32_t height_, const int32_t x_ /* = 0 */, const int32_t y_ /* = 0 */ )
Expand Down Expand Up @@ -2662,6 +2697,9 @@ namespace fheroes2
return;
}

// 'in' and' out' images should have the same layers.
assert( in.singleLayer() == out.singleLayer() );

const int32_t widthIn = in.width();
const int32_t widthOut = out.width();

Expand Down
36 changes: 25 additions & 11 deletions src/engine/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
***************************************************************************/
#pragma once

#include <cassert>
#include <cstdint>
#include <memory>
#include <utility>
Expand All @@ -28,7 +29,7 @@

namespace fheroes2
{
// Image contains image layer and transform layer.
// Image contains always contains an image layer and if image is not a single-layer than also a transform layer:
// - image layer contains visible pixels which are copy to a destination image
// - transform layer is used to apply some transformation to an image on which we draw the current one. For example, shadowing
class Image
Expand Down Expand Up @@ -63,27 +64,35 @@ namespace fheroes2

uint8_t * transform()
{
return _data.get() + width() * height();
// Why do you want to get transform layer from the single-layer image?
assert( !_singleLayer );

return _transformData.get();
}

const uint8_t * transform() const
{
return _data.get() + width() * height();
// Why do you want to get transform layer from the single-layer image?
assert( !_singleLayer );

return _transformData.get();
}

bool empty() const
{
return !_data;
return !_imageData;
}

void reset(); // makes image fully transparent (transform layer is set to 1)
void clear(); // makes the image empty
// Set all data in the image layer to 0 and make double-layer images fully transparent (transform layer is set to 1).
void reset();

// Make the image empty.
void clear();

// Fill 'image' layer with given value, setting 'transform' layer to 0.
// Fill 'image' layer with given value, setting the double-layer images 'transform' layer to 0.
void fill( const uint8_t value );

// This is an optional indicator for image processing functions.
// The whole image still consists of 2 layers but transform layer might be ignored in computations.
// This is an indicator for image processing functions. The single-layer image does not contain transform layer.
bool singleLayer() const
{
return _singleLayer;
Expand All @@ -93,6 +102,9 @@ namespace fheroes2
// The name of this method starts from _ on purpose to do not mix with other public methods.
void _disableTransformLayer()
{
// Free the transform layer memory preserving the image layer data.
_transformData.reset();

_singleLayer = true;
}

Expand All @@ -101,9 +113,11 @@ namespace fheroes2

int32_t _width{ 0 };
int32_t _height{ 0 };
std::unique_ptr<uint8_t[]> _data; // holds 2 image layers

// Only for images which are not used for any other operations except displaying on screen.
std::unique_ptr<uint8_t[]> _imageData;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved
std::unique_ptr<uint8_t[]> _transformData;
Districh-ru marked this conversation as resolved.
Show resolved Hide resolved

// Image may be set as single-layer only for images which are not used for any other operations except displaying on screen.
bool _singleLayer{ false };
};

Expand Down
4 changes: 4 additions & 0 deletions src/engine/image_tool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ namespace fheroes2

while ( true ) {
if ( 0 == *data ) { // 0x00 - end of row
if ( noTransformLayer && ( static_cast<int32_t>( posX ) < width ) ) {
noTransformLayer = false;
}

imageData += width;
imageTransform += width;
posX = 0;
Expand Down
6 changes: 2 additions & 4 deletions src/engine/localevent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1401,10 +1401,8 @@ void LocalEvent::HandleRenderDeviceResetEvent()
{
// All textures has to be recreated. The only way to do it is to reset everything and render it back.
fheroes2::Display & display = fheroes2::Display::instance();
fheroes2::Image temp( display.width(), display.height() );
if ( display.singleLayer() ) {
temp._disableTransformLayer();
}
fheroes2::Image temp;
temp._disableTransformLayer();

fheroes2::Copy( display, temp );
display.release();
Expand Down
12 changes: 7 additions & 5 deletions src/engine/screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ namespace
return nullptr;
}

assert( !icon.singleLayer() );

const uint32_t width = icon.width();
const uint32_t height = icon.height();

Expand Down Expand Up @@ -446,8 +448,9 @@ namespace

void update( const fheroes2::Image & image, int32_t offsetX, int32_t offsetY ) override
{
if ( image.empty() ) {
if ( image.empty() || image.singleLayer() ) {
// What are you trying to do? Set an invisible cursor? Use hide() method!
// Cursor image should have the transform layer.
assert( 0 );
return;
}
Expand Down Expand Up @@ -1573,8 +1576,10 @@ namespace fheroes2
void Display::setResolution( ResolutionInfo info )
{
if ( width() > 0 && height() > 0 && info.gameWidth == width() && info.gameHeight == height() && info.screenWidth == _screenSize.width
&& info.screenHeight == _screenSize.height ) // nothing to resize
&& info.screenHeight == _screenSize.height ) {
// Nothing to resize.
return;
}

const bool isFullScreen = _engine->isFullScreen();

Expand All @@ -1590,9 +1595,6 @@ namespace fheroes2

Image::resize( info.gameWidth, info.gameHeight );
_screenSize = { info.screenWidth, info.screenHeight };

// To detect some UI artifacts by invalid code let's put all transform data into pixel skipping mode.
std::fill( transform(), transform() + width() * height(), static_cast<uint8_t>( 1 ) );
}

Display & Display::instance()
Expand Down
24 changes: 13 additions & 11 deletions src/engine/zzlib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

#include "zzlib.h"

#include <algorithm>
#include <cstring>
#include <ostream>
#include <vector>
Expand Down Expand Up @@ -193,29 +192,32 @@ bool ZStreamBuf::write( const std::string & fn, const bool append /* = false */
return !sf.fail();
}

fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer )
fheroes2::Image CreateImageFromZlib( const int32_t width, const int32_t height, const uint8_t * imageData, const size_t imageSize, const bool doubleLayer )
{
if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 )
return fheroes2::Image();
if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 ) {
return {};
}

const std::vector<uint8_t> & uncompressedData = zlibDecompress( imageData, imageSize );
if ( doubleLayer && ( uncompressedData.size() & 1 ) == 1 ) {
return fheroes2::Image();
return {};
}

const size_t uncompressedSize = doubleLayer ? uncompressedData.size() / 2 : uncompressedData.size();

if ( static_cast<size_t>( width * height ) != uncompressedSize )
return fheroes2::Image();
if ( static_cast<size_t>( width ) * height != uncompressedSize ) {
return {};
}

fheroes2::Image out( width, height );
fheroes2::Image out;
if ( !doubleLayer ) {
out._disableTransformLayer();
}
out.resize( width, height );

std::memcpy( out.image(), uncompressedData.data(), uncompressedSize );
if ( doubleLayer ) {
std::memcpy( out.transform(), uncompressedData.data() + uncompressedSize, uncompressedSize );
}
else {
std::fill( out.transform(), out.transform() + uncompressedSize, static_cast<uint8_t>( 0 ) );
}
return out;
}
2 changes: 1 addition & 1 deletion src/engine/zzlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ class ZStreamBuf : public StreamBuf
bool write( const std::string & fn, const bool append = false ) const;
};

fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer );
fheroes2::Image CreateImageFromZlib( const int32_t width, const int32_t height, const uint8_t * imageData, const size_t imageSize, const bool doubleLayer );

#endif
Loading