Skip to content

Conversation

@stan4dbunny
Copy link
Contributor

@stan4dbunny stan4dbunny commented Nov 5, 2025

Fixes #379

I have implemented an option to use compressed color maps when the game is running (i.e when not editing). When the compress option is used and the scene is saved, the regions will all get a compressed color map. When the game is run (either launched from editor or when exported), the compressed color map will be used and the uncompressed color map will be unloaded.

I have tested that the compressed texture is used instead of the normal one when the game is running with the option enabled by analyzing the frame in Renderdoc. The compressed color map is also updated after the terrain has been sculpted/painted in the editor.

With the compressed color map, the color map size is 4x smaller.

image image

I have also added some documentation in the existing style.

One thing that I noticed is that when there is a large terrain with a lot of regions, the saving can take a long time (several minutes) when the option has been clicked, but this should only happen once. Maybe this time could be reduced if the compression happend on the entire color map at once instead of for the tile in each region.

@stan4dbunny stan4dbunny changed the title Option to use compressed color map Option to use compressed color map + height map 16 bit bug fix Nov 5, 2025
Copy link
Owner

@TokisanGames TokisanGames left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. Here are some notes:

Your commits are coming on another account. You probably want to fix that before I merge. You can also squash your commits at the same time.

Image

@stan4dbunny stan4dbunny marked this pull request as ready for review November 6, 2025 15:44
@stan4dbunny stan4dbunny marked this pull request as draft November 6, 2025 15:45
@stan4dbunny stan4dbunny changed the title Option to use compressed color map + height map 16 bit bug fix Option to use compressed color map Nov 7, 2025
@TokisanGames
Copy link
Owner

#825 has been merged. From now on the principle is any setter function that is called with the same value is silently ignored. Logs, signals, and updates are not triggered unless the value is different. Once you rebase, you can use SET_IF_DIFF(_member_var, p_new_param); which will return if they are the same or set it if different, for most values including native types, pointers, and Godot Dictionaries and Arrays (compared by internal pointer). Occasionally a bit more complexity is warranted so a few setters accomplish the same idea with different means.

Copy link
Owner

@TokisanGames TokisanGames left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job so far with the code and the testing. Running the demo shows the right format change and VRAM reduction.

Image Image Image

I have noticed one issue so far:

If compression mode is set and free_uncompressed_color_maps is false, it still frees the uncompressed colormap, when it shouldn't.

var region: Terrain3DRegion = $Terrain3D.data.get_region(Vector2i(0,0))
print("Color map: ", region.color_map)
print("Compressed color map: ", region.compressed_color_map)

color_compression = bptc
free_uncompressed_color_maps = false (same if true)
Color map: <Object#null>
Compressed color map: <Image#-9223371995985476204>

color_compression = none
free_uncompressed_color_maps = false (same if true)
Color map: <Image#-9223371996035807859>
Compressed color map: <Object#null>

You can also rebase and squash all of your commits.

@stan4dbunny
Copy link
Contributor Author

Hello! I have now fixed the changes you requested. Let me know if anything should be changed.

Just wondering, but regarding squashing would it not be easier for you to squash the commits through the "squash and merge" GitHub option?

@TokisanGames
Copy link
Owner

Hello! I have now fixed the changes you requested. Let me know if anything should be changed.

Great, thank you.

Just wondering, but regarding squashing would it not be easier for you to squash the commits through the "squash and merge" GitHub option?

No, it needs to be squashed and rebased. Otherwise it will make a mess of the history. Github does one or other other, not both. And it doesn't rebase if there are conflicts, which is the case.

There are currently conflicts and 25 commits. Please squash your commits down to two: code changes in one, and docs in the other. Then rebase onto the latest main and resolve any conflicts. The docs explain how, and I can help on discord.

When I prepare it for merging I will tweak the code and docs, generate the rst files, and csharp bindings. It's helpful if I don't also have to squash, rebase, and fix conflicts myself.

@stan4dbunny stan4dbunny force-pushed the baking_color_map branch 2 times, most recently from 25c7dc8 to b93c7d6 Compare December 3, 2025 15:30
@TokisanGames TokisanGames moved this to In Progress in Terrain3D Roadmap Dec 4, 2025
@TokisanGames TokisanGames added this to the 1.1 milestone Dec 4, 2025
@TokisanGames TokisanGames marked this pull request as ready for review December 8, 2025 20:53
…runtime. Option to free the uncompressed color map during runtime and export when the compression mode is set.
Copy link
Owner

@TokisanGames TokisanGames left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking better, thank you. I have now taken over this PR. Comments are for your edification only.

Pending:

  • Review Terrain3DRegion:
    • free_uncompressed_color_map still frees when disabled.
    • rework santisize_maps()


void Terrain3D::set_color_compression_mode(const ColorCompressMode p_color_compression_mode) {
SET_IF_DIFF(_color_compression_mode, p_color_compression_mode);
switch (_color_compression_mode) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to convert this on the very rare get godot version call, then we don't need to store the extra variable.

}
LOG(INFO, "Setting compression mode for color maps: ", _image_color_compression_mode);
TypedArray<Terrain3DRegion> regions = _data->get_regions_active();
for (int i = 0; i < regions.size(); i++) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now use ranged for loops

	for (Ref<Terrain3DRegion> region : regions) {
		region->set_modified(true);
	}



func _customize_scene(scene: Node, path: String) -> Node:
var _terrain: Terrain3D = scene.find_child("Terrain3D", true)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ is our convention for class private variables like _hash.
terrain is local, so does not use _.

# Includes
const Terrain3DUI: Script = preload("res://addons/terrain_3d/src/ui.gd")
const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.gd")
const ExportPlugin = preload("res://addons/terrain_3d/src/export_plugin.gd")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: Script

// File I/O
void save_directory(const String &p_dir);
void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false);
void save_region(const Vector2i &p_region_loc, const String &p_dir, const bool p_16_bit = false, const Image::CompressMode p_color_compression_mode = Image::COMPRESS_MAX);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use our color compression mode.

src/terrain_3d.h Outdated
SIZE_2048 = 2048,
};

enum ColorCompressMode {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a property of the maps, so should be in Terrain3DRegion, along with the Image Format.

// Generate all or only those marked edited
if (region && (p_all_regions || region->is_edited())) {
region->get_color_map()->generate_mipmaps();
region->get_map(TYPE_COLOR)->generate_mipmaps();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this cannot be done on a compressed map. So, it must be done on the uncompressed if valid or not at all.

void set_compressed_color_map(const Ref<Image> &p_map);
Ref<Image> get_compressed_color_map() const { return _compressed_color_map; }
void free_uncompressed_color_map();
void sanitize_maps(const bool p_free_uncompressed_color_maps);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have a default, rather than changing all usage cases of it.

@TokisanGames TokisanGames self-assigned this Dec 8, 2025
ClassDB::bind_method(D_METHOD("set_color_map", "map"), &Terrain3DRegion::set_color_map);
ClassDB::bind_method(D_METHOD("get_color_map"), &Terrain3DRegion::get_color_map);
ClassDB::bind_method(D_METHOD("sanitize_maps"), &Terrain3DRegion::sanitize_maps);
ClassDB::bind_method(D_METHOD("set_compressed_color_map", "map"), &Terrain3DRegion::set_compressed_color_map);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR doesn't currently sanitize() compressed color maps when set. I don't think we even need to offer the ability to set them to the user. I think we offer: get, clear, or generate. Then we don't need to sanitize, since the raw color map is already sanitized.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, but I see we need this function in order to load the compressed color map from disk.

_control_map = map;
if (p_free_uncompressed_color_maps) {
free_uncompressed_color_map();
return;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right function to clear the uncompressed maps. And returning here I'm pretty sure is a bug. The purpose of this function is to ensure the existing data is clean and safe to use, not to free data or optimize memory.

case TYPE_COLOR:
if (!IS_EDITOR && _compressed_color_map.is_valid()) {
return *_compressed_color_map;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the right place for this either. The expectation should be if you ask for a color map you get the editable color map. We need a new function to get the active one which implies the one you get may be compressed and uneditable.

Other functions like set_maps(), get_maps(), get_data() don't even consider the compressed map. It needs to be all or nothing. I think it's cleaner to be nothing - so the compressed map is only created on save, freed in the editor, only accessed by get_compressed_color_map() or get_active_color_map(), everything else gives you the editable color map, though it may be null.

@TokisanGames
Copy link
Owner

TokisanGames commented Dec 10, 2025

I have reorganized some of the code. Now:

  • All functions that get the colormap, are unchanged, returning the editable colormap().
  • Maps and functions all refer to color map and compressed color map. No more "uncompressed".
  • get_active_color_map() returns the compressed map if not editor and valid, which update_maps() uses as read only.
  • compressed maps are freed in the editor. Viewing a res file after it has been loaded will show an empty compressed map. Closing a scene and viewing the res will show the compressed and uncompressed map.
  • color maps are freed in game if the option is chosen and the compressed map is valid.
  • compress_color_map() is now available

Next, I will pull a commit from my dev mode PR to entirely disable the color map.

Then I'll remove free_color_map and instead introduce a color map mode with these options:

  • Disabled - saved data is unchanged; editing is disabled; new regions create maps; compressed and uncompressed are freed in editor and game;
  • Cleared - disabled, and maps are cleared on save
  • Editable - current - no compressed map saved or used, editable in game
  • Compressed - editable in editor; compressed freed in editor; uncompressed freed in game and stripped on export. This will also enable the compression options.

Does this look like the full list of desired possibilities?

@Xtarsia
Copy link
Collaborator

Xtarsia commented Dec 10, 2025

Have exported projects to web/mobile been tested for this yet? Ommiting to test those has caught me out before.

@TokisanGames TokisanGames marked this pull request as draft January 21, 2026 10:14
@TokisanGames TokisanGames modified the milestones: 1.1, 1.2 Jan 21, 2026
@TokisanGames
Copy link
Owner

There might be an issue with compressed maps on linux.

https://discord.com/channels/691957978680786944/1065519581013229578/1463464825316180061

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Add an option to disable the color map, to save ram/vram

3 participants