WIP
This commit is contained in:
parent
3bcece4bc3
commit
5e30f3f2ab
16
ENGINE_TODO.md
Normal file
16
ENGINE_TODO.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Todo for engine feature polish
|
||||||
|
|
||||||
|
## Particles
|
||||||
|
- Particle counts quite limited, increase.
|
||||||
|
|
||||||
|
## Post-processing
|
||||||
|
- DOF and bloom are both doing this thing where they draw to a small resolution buffer.
|
||||||
|
- It's performant now compared to old approach, but is bad. We need to do the blur thing
|
||||||
|
in a shader X and Y separately for both.
|
||||||
|
|
||||||
|
## Trile rendering
|
||||||
|
- Too many triangles in meshgen.
|
||||||
|
- Shadowmap should move with camera.
|
||||||
|
- Reflection trile rendering is taking forever compared to gbuffer pass even though it
|
||||||
|
shouldn't really be doing much more.
|
||||||
|
-
|
||||||
44
first.jai
44
first.jai
@ -47,6 +47,46 @@ build_options_from_args :: (args: []string) -> Trueno_Build_Options {
|
|||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scans `dir` recursively and returns the size of the largest file whose
|
||||||
|
// short name ends with `ext`. Returns `floor` when no matching file exists.
|
||||||
|
max_asset_file_size :: (dir: string, ext: string, floor: s64 = 0) -> s64 {
|
||||||
|
Ctx :: struct { ext: string; max: s64; }
|
||||||
|
ctx: Ctx;
|
||||||
|
ctx.ext = ext;
|
||||||
|
visit_files(dir, true, *ctx, (info: *File_Visit_Info, c: *Ctx) {
|
||||||
|
if info.is_directory return;
|
||||||
|
if !ends_with(info.short_name, c.ext) return;
|
||||||
|
f, ok := file_open(info.full_name);
|
||||||
|
defer if ok file_close(*f);
|
||||||
|
if !ok return;
|
||||||
|
size, ok2 := file_length(f);
|
||||||
|
if ok2 && size > c.max then c.max = size;
|
||||||
|
});
|
||||||
|
return max(floor, ctx.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
add_asset_buffer_sizes_to_compiler_strings :: (trueno_opts: Trueno_Build_Options, w: Workspace) {
|
||||||
|
pack_dir := ifx trueno_opts.test_exe_engine || trueno_opts.test_exe_game then "./test_packs" else "./packs";
|
||||||
|
game_resources_dir := ifx trueno_opts.test_exe_engine || trueno_opts.test_exe_game then "./test_game/resources" else "./game/resources";
|
||||||
|
|
||||||
|
sizes: [6]s64;
|
||||||
|
sizes[0] = max_asset_file_size(pack_dir, ".pack", 1 * 1024 * 1024);
|
||||||
|
sizes[1] = max(
|
||||||
|
max_asset_file_size(game_resources_dir, "world.json", 64 * 1024),
|
||||||
|
max_asset_file_size(game_resources_dir, "index.world", 64 * 1024)
|
||||||
|
);
|
||||||
|
sizes[2] = max_asset_file_size(game_resources_dir, "chunks.bin", 1 * 1024 * 1024);
|
||||||
|
sizes[3] = max_asset_file_size(game_resources_dir, ".shgrid", 1 * 1024 * 1024);
|
||||||
|
sizes[4] = max_asset_file_size(game_resources_dir, "rdm_atlas.rdm", 16 * 1024 * 1024);
|
||||||
|
sizes[5] = max_asset_file_size(game_resources_dir, "rdm_manifest.json", 256 * 1024);
|
||||||
|
|
||||||
|
names :: string.["MAX_PACK_SIZE", "MAX_WORLD_SIZE", "MAX_CHUNKS_SIZE",
|
||||||
|
"MAX_SHGRID_SIZE", "MAX_RDM_ATLAS_SIZE", "MAX_RDM_MANIFEST_SIZE"];
|
||||||
|
for i: 0..5 {
|
||||||
|
add_build_string(tprint("% :: %;\n", names[i], sizes[i]), w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
add_trueno_opts_to_compiler_strings :: (trueno_opts : Trueno_Build_Options, w: Workspace) {
|
add_trueno_opts_to_compiler_strings :: (trueno_opts : Trueno_Build_Options, w: Workspace) {
|
||||||
#import "String";
|
#import "String";
|
||||||
bool_to_string :: (b: bool) -> string {
|
bool_to_string :: (b: bool) -> string {
|
||||||
@ -127,6 +167,7 @@ native_build :: (opts: Build_Options, trueno_opts: Trueno_Build_Options) {
|
|||||||
|
|
||||||
compiler_begin_intercept(w);
|
compiler_begin_intercept(w);
|
||||||
add_trueno_opts_to_compiler_strings(trueno_opts, w);
|
add_trueno_opts_to_compiler_strings(trueno_opts, w);
|
||||||
|
add_asset_buffer_sizes_to_compiler_strings(trueno_opts, w);
|
||||||
add_build_file("src/platform_specific/main_native.jai", w);
|
add_build_file("src/platform_specific/main_native.jai", w);
|
||||||
add_shaders_to_workspace(w);
|
add_shaders_to_workspace(w);
|
||||||
|
|
||||||
@ -178,6 +219,7 @@ wasm_build :: (opts: Build_Options, trueno_opts: Trueno_Build_Options) {
|
|||||||
compiler_begin_intercept(w);
|
compiler_begin_intercept(w);
|
||||||
|
|
||||||
add_trueno_opts_to_compiler_strings(trueno_opts, w);
|
add_trueno_opts_to_compiler_strings(trueno_opts, w);
|
||||||
|
add_asset_buffer_sizes_to_compiler_strings(trueno_opts, w);
|
||||||
add_build_file("src/platform_specific/main_web.jai", w);
|
add_build_file("src/platform_specific/main_web.jai", w);
|
||||||
add_shaders_to_workspace(w);
|
add_shaders_to_workspace(w);
|
||||||
|
|
||||||
@ -225,4 +267,6 @@ wasm_build :: (opts: Build_Options, trueno_opts: Trueno_Build_Options) {
|
|||||||
#import "Compiler";
|
#import "Compiler";
|
||||||
#import "Process";
|
#import "Process";
|
||||||
#import "File";
|
#import "File";
|
||||||
|
#import "String";
|
||||||
|
#import "File_Utilities";
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
_______________
|
||||||
|
Vulkan Version:
|
||||||
|
- available: 1.4.309
|
||||||
|
- requesting: 1.3.0
|
||||||
|
______________________
|
||||||
|
Used Instance Layers :
|
||||||
|
VK_LAYER_KHRONOS_validation
|
||||||
|
|
||||||
|
Used Instance Extensions :
|
||||||
|
____________________
|
||||||
|
Devices : 1
|
||||||
|
0: AMD Radeon RX 6950 XT
|
||||||
|
- Compatible
|
||||||
|
Compatible physical devices found : 1
|
||||||
|
Using Device:
|
||||||
|
- Device Name : AMD Radeon RX 6950 XT
|
||||||
|
- Vendor : AMD
|
||||||
|
- Driver Version : 2.0.341
|
||||||
|
- API Version : 1.4.308
|
||||||
|
- Device Type : Discrete GPU
|
||||||
|
________________________
|
||||||
|
Used Device Extensions :
|
||||||
|
VK_KHR_deferred_host_operations
|
||||||
|
VK_KHR_acceleration_structure
|
||||||
|
VK_KHR_ray_query
|
||||||
|
|
||||||
|
BLAS Compaction: 1.8MB -> 0.6MB (1.2MB saved, 65.9% smaller)
|
||||||
20
perf
Normal file
20
perf
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FRAME PRE OPTIMIZATION!
|
||||||
|
|
||||||
|
|
||||||
|
| Pass | Targets | Duration (µs) | What it does |
|
||||||
|
|--------------|---------|---------------|--------------|
|
||||||
|
| Colour Pass #1 | 1T+D | 257.64 | Draws all triles and billboards with the shadowmap shader into a shadowmap|
|
||||||
|
| Colour Pass #2 | 1T+D | 2226.80 | Draws triles and billboard for planar reflection, does viewport culling, uses mostly same shaders as main pass |
|
||||||
|
| Colour Pass #3 | 2T+D | 886.92 | Gbuffer pass. All triles and billboards. Special gbuffer shader. |
|
||||||
|
| Colour Pass #4 | 1T+D | 727.08 | SSAO texture draw pass. Output 4k |
|
||||||
|
| Colour Pass #5 | 1T+D | 500.60 | SSAO blur, output 1920x1066 |
|
||||||
|
| Colour Pass #6 | 1T+D | 4201.48 | Main pass, draws water (284us) draws billboards, samples previous buffers, draws particles (don't seem to be heavy, max 21us) draws triles draws billboards |
|
||||||
|
| Colour Pass #7 | 1T+D | 842.84 | Extracts bright parts and blurs? For bloom |
|
||||||
|
| Colour Pass #8 | 1T+D | 1069.52 | Applies bloom |
|
||||||
|
| Colour Pass #9 | 1T+D | 1648.36 | Blurs whole image for DOF purposes |
|
||||||
|
| Colour Pass #10 | 1T+D | 888.84 | I think this is dilution for the blurred image but not totally sure. |
|
||||||
|
| Colour Pass #11 | 1T+D | 1105.04 | Mixes blurred and original for DOF effect |
|
||||||
|
| Colour Pass #12 | 1T+D | 288.28 | Final pass, does color correction and some effects |
|
||||||
|
| **Total** | | **14643.40** | |
|
||||||
|
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
master_volume 0.475357
|
master_volume 0.364372
|
||||||
music_volume 0.522385
|
music_volume 1
|
||||||
sfx_volume 1
|
sfx_volume 1
|
||||||
fullscreen 0
|
fullscreen 1
|
||||||
|
|||||||
@ -6,57 +6,60 @@ Pool :: #import "Pool";
|
|||||||
|
|
||||||
#load "loaders.jai";
|
#load "loaders.jai";
|
||||||
|
|
||||||
MAIN_CHUNK_BUFFER_SIZE : s64 : 256 * 1024;
|
// MAX_PACK_SIZE, MAX_WORLD_SIZE, MAX_CHUNKS_SIZE, MAX_SHGRID_SIZE,
|
||||||
RDM_CHUNK_BUFFER_SIZE : s64 : 64 * 1024 * 1024;
|
// MAX_RDM_ATLAS_SIZE, MAX_RDM_MANIFEST_SIZE are injected as build strings
|
||||||
|
// by first.jai after scanning the actual asset files at compile time.
|
||||||
|
|
||||||
Active_Fetch :: struct {
|
MAX_FETCH_SLOTS :: 16;
|
||||||
occupied : bool;
|
|
||||||
req : Fetch_Request;
|
|
||||||
accumulated : [..]u8;
|
|
||||||
chunk_buf : []u8;
|
|
||||||
}
|
|
||||||
|
|
||||||
#scope_export
|
#scope_export
|
||||||
|
|
||||||
CHANNEL_MAIN : u32 : 0;
|
// Replaces the old should_block / should_block_engine bool pair.
|
||||||
CHANNEL_RDM : u32 : 1;
|
Load_Priority :: enum u8 {
|
||||||
|
BLOCK_ENGINE; // must finish before the first game frame
|
||||||
|
LOADING_SCREEN; // show loading screen while pending
|
||||||
|
BACKGROUND; // stream silently; game runs normally
|
||||||
|
}
|
||||||
|
|
||||||
Fetch_Type :: enum {
|
Fetch_Type :: enum {
|
||||||
PACK;
|
PACK;
|
||||||
WORLD;
|
WORLD;
|
||||||
WORLD_CHUNKS;
|
WORLD_CHUNKS;
|
||||||
RDM_ATLAS;
|
RDM_ATLAS;
|
||||||
RDM_LOOKUP;
|
RDM_MANIFEST;
|
||||||
SHGRID;
|
SHGRID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Fetch_Request :: struct {
|
Fetch_Request :: struct {
|
||||||
type : Fetch_Type;
|
type : Fetch_Type;
|
||||||
path : string;
|
path : string;
|
||||||
|
priority : Load_Priority;
|
||||||
|
|
||||||
// Pack
|
// PACK
|
||||||
pack_name : string;
|
pack_name : string;
|
||||||
should_block : bool;
|
|
||||||
should_block_engine : bool;
|
|
||||||
|
|
||||||
// World / RDM
|
// WORLD / RDM / SH
|
||||||
world_name : string;
|
world_name : string;
|
||||||
chunk_key : Chunk_Key;
|
chunk_key : Chunk_Key;
|
||||||
|
|
||||||
// Atlas GPU image held between RDM_ATLAS and its paired RDM_LOOKUP fetch.
|
// WORLD_CHUNKS: JSON carried forward from the WORLD fetch
|
||||||
rdm_pending_atlas : sg_image;
|
world_json_data : []u8;
|
||||||
|
}
|
||||||
|
|
||||||
// Copy of world.json carried between WORLD and WORLD_CHUNKS fetches.
|
// One in-flight sfetch request. `buf` is allocated when dispatched and
|
||||||
world_json_data : []u8;
|
// freed (or handed to Loaded_Pack) when the callback fires.
|
||||||
|
Fetch_Slot :: struct {
|
||||||
|
occupied : bool;
|
||||||
|
req : Fetch_Request;
|
||||||
|
buf : []u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
Asset_Manager :: struct {
|
Asset_Manager :: struct {
|
||||||
active : [2]Active_Fetch;
|
slots : [MAX_FETCH_SLOTS]Fetch_Slot;
|
||||||
main_queue : [..]Fetch_Request;
|
queue : [..]Fetch_Request;
|
||||||
rdm_queue : [..]Fetch_Request;
|
loadedPacks : [..]Loaded_Pack;
|
||||||
loadedPacks : [..]Loaded_Pack;
|
pending_hot_reload : bool;
|
||||||
pending_hot_reload : bool;
|
hot_reload_unpause_pending : bool;
|
||||||
hot_reload_unpause_pending : bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_asset_manager : Asset_Manager;
|
g_asset_manager : Asset_Manager;
|
||||||
@ -66,49 +69,48 @@ g_asset_manager : Asset_Manager;
|
|||||||
#load "rdm_loader.jai";
|
#load "rdm_loader.jai";
|
||||||
#load "sh_loader.jai";
|
#load "sh_loader.jai";
|
||||||
|
|
||||||
|
fetch_type_buffer_size :: (type: Fetch_Type) -> s64 {
|
||||||
|
if #complete type == {
|
||||||
|
case .PACK; return MAX_PACK_SIZE;
|
||||||
|
case .WORLD; return MAX_WORLD_SIZE;
|
||||||
|
case .WORLD_CHUNKS; return MAX_CHUNKS_SIZE;
|
||||||
|
case .RDM_ATLAS; return MAX_RDM_ATLAS_SIZE;
|
||||||
|
case .RDM_MANIFEST; return MAX_RDM_MANIFEST_SIZE;
|
||||||
|
case .SHGRID; return MAX_SHGRID_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fetch_callback :: (res: *sfetch_response_t) #c_call {
|
fetch_callback :: (res: *sfetch_response_t) #c_call {
|
||||||
push_context,defer_pop default_context;
|
push_context,defer_pop default_context;
|
||||||
|
|
||||||
af := *g_asset_manager.active[res.channel];
|
// sokol copied the slot index into res.user_data when we sent the request.
|
||||||
|
slot_idx := (cast(*u32) res.user_data).*;
|
||||||
if res.fetched {
|
slot := *g_asset_manager.slots[slot_idx];
|
||||||
chunk_count := res.data.size.(s64);
|
|
||||||
needed := af.accumulated.count + chunk_count;
|
|
||||||
if af.accumulated.allocated < needed {
|
|
||||||
new_cap := max(needed, max(af.accumulated.allocated * 2, 1024 * 1024));
|
|
||||||
array_reserve(*af.accumulated, new_cap);
|
|
||||||
}
|
|
||||||
memcpy(af.accumulated.data + af.accumulated.count, res.data.ptr, chunk_count);
|
|
||||||
af.accumulated.count = needed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !res.finished then return;
|
if !res.finished then return;
|
||||||
|
|
||||||
// Mark channel free before processing so that enqueued follow-up requests
|
slot.occupied = false;
|
||||||
// (WORLD -> WORLD_CHUNKS, RDM_ATLAS -> RDM_LOOKUP) are visible on the next tick.
|
req := slot.req;
|
||||||
af.occupied = false;
|
|
||||||
req := af.req;
|
|
||||||
|
|
||||||
if res.failed {
|
if res.failed {
|
||||||
handle_fetch_failed(*req);
|
handle_fetch_failed(*req);
|
||||||
array_reset(*af.accumulated);
|
free(slot.buf.data);
|
||||||
|
slot.buf = .{};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data : []u8;
|
data: []u8;
|
||||||
data.data = af.accumulated.data;
|
data.data = cast(*u8) res.data.ptr;
|
||||||
data.count = af.accumulated.count;
|
data.count = cast(s64) res.data.size;
|
||||||
|
|
||||||
process_completed_fetch(*req, data);
|
process_completed_fetch(*req, data);
|
||||||
|
|
||||||
if req.type == .PACK {
|
if req.type == .PACK {
|
||||||
// process_completed_fetch either stored data in Loaded_Pack.data_buffer or
|
// process_completed_fetch transferred ownership to Loaded_Pack.data_buffer.
|
||||||
// freed it on parse failure. Either way, zero af.accumulated without freeing.
|
slot.buf = .{};
|
||||||
af.accumulated.data = null;
|
|
||||||
af.accumulated.count = 0;
|
|
||||||
af.accumulated.allocated = 0;
|
|
||||||
} else {
|
} else {
|
||||||
array_reset(*af.accumulated);
|
free(slot.buf.data);
|
||||||
|
slot.buf = .{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,12 +122,12 @@ handle_fetch_failed :: (req: *Fetch_Request) {
|
|||||||
|
|
||||||
case .WORLD;
|
case .WORLD;
|
||||||
if ends_with(req.path, "world.json") {
|
if ends_with(req.path, "world.json") {
|
||||||
fallback_req : Fetch_Request;
|
fallback: Fetch_Request;
|
||||||
fallback_req.type = .WORLD;
|
fallback.type = .WORLD;
|
||||||
fallback_req.world_name = req.world_name;
|
fallback.priority = req.priority;
|
||||||
fallback_req.path = sprint("%/worlds/%/index.world", GAME_RESOURCES_DIR, req.world_name);
|
fallback.world_name = req.world_name;
|
||||||
fallback_req.should_block = true;
|
fallback.path = sprint("%/worlds/%/index.world", GAME_RESOURCES_DIR, req.world_name);
|
||||||
array_add(*g_asset_manager.main_queue, fallback_req);
|
array_add(*g_asset_manager.queue, fallback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log_error("Failed to load world '%'", req.world_name);
|
log_error("Failed to load world '%'", req.world_name);
|
||||||
@ -135,11 +137,10 @@ handle_fetch_failed :: (req: *Fetch_Request) {
|
|||||||
log_error("Failed to load chunks.bin for world '%'", req.world_name);
|
log_error("Failed to load chunks.bin for world '%'", req.world_name);
|
||||||
|
|
||||||
case .RDM_ATLAS;
|
case .RDM_ATLAS;
|
||||||
log_error("RDM: failed to load atlas for chunk %", req.chunk_key);
|
log_info("RDM: no global atlas for world '%' (ok if none baked yet)", req.world_name);
|
||||||
|
|
||||||
case .RDM_LOOKUP;
|
case .RDM_MANIFEST;
|
||||||
if req.rdm_pending_atlas.id != 0 then sg_destroy_image(req.rdm_pending_atlas);
|
log_info("RDM: no manifest for world '%' (ok if none baked yet)", req.world_name);
|
||||||
log_error("RDM: failed to load lookup for chunk %", req.chunk_key);
|
|
||||||
|
|
||||||
case .SHGRID;
|
case .SHGRID;
|
||||||
sh_loader_handle_failed(req);
|
sh_loader_handle_failed(req);
|
||||||
@ -171,13 +172,13 @@ process_completed_fetch :: (req: *Fetch_Request, data: []u8) {
|
|||||||
if is_json {
|
if is_json {
|
||||||
json_copy := NewArray(data.count, u8, false);
|
json_copy := NewArray(data.count, u8, false);
|
||||||
memcpy(json_copy.data, data.data, data.count);
|
memcpy(json_copy.data, data.data, data.count);
|
||||||
chunks_req : Fetch_Request;
|
chunks_req: Fetch_Request;
|
||||||
chunks_req.type = .WORLD_CHUNKS;
|
chunks_req.type = .WORLD_CHUNKS;
|
||||||
|
chunks_req.priority = req.priority;
|
||||||
chunks_req.world_name = req.world_name;
|
chunks_req.world_name = req.world_name;
|
||||||
chunks_req.path = sprint("%/worlds/%/chunks.bin", GAME_RESOURCES_DIR, req.world_name);
|
chunks_req.path = sprint("%/worlds/%/chunks.bin", GAME_RESOURCES_DIR, req.world_name);
|
||||||
chunks_req.should_block = true;
|
|
||||||
chunks_req.world_json_data = json_copy;
|
chunks_req.world_json_data = json_copy;
|
||||||
array_add(*g_asset_manager.main_queue, chunks_req);
|
array_add(*g_asset_manager.queue, chunks_req);
|
||||||
} else {
|
} else {
|
||||||
world, ok := load_world_from_data(data);
|
world, ok := load_world_from_data(data);
|
||||||
if ok {
|
if ok {
|
||||||
@ -214,88 +215,44 @@ process_completed_fetch :: (req: *Fetch_Request, data: []u8) {
|
|||||||
|
|
||||||
header_size := cast(s64) size_of(RDM_File_Header);
|
header_size := cast(s64) size_of(RDM_File_Header);
|
||||||
if data.count < header_size {
|
if data.count < header_size {
|
||||||
log_error("RDM: atlas too small for chunk %", req.chunk_key);
|
log_error("RDM: global atlas too small (world %)", req.world_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
header := cast(*RDM_File_Header) data.data;
|
header := cast(*RDM_File_Header) data.data;
|
||||||
if header.magic != RDM_FILE_MAGIC {
|
if header.magic != RDM_FILE_MAGIC {
|
||||||
log_error("RDM: bad atlas magic for chunk %", req.chunk_key);
|
log_error("RDM: bad atlas magic (world %)", req.world_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
atlas_pixel_bytes := cast(u64) header.width * cast(u64) header.height * 4 * size_of(float);
|
atlas_pixel_bytes := cast(u64) header.width * cast(u64) header.height * 4 * size_of(u16);
|
||||||
atlas_imgdata : sg_image_data;
|
atlas_imgdata: sg_image_data;
|
||||||
atlas_imgdata.subimage[0][0] = .{ data.data + header_size, atlas_pixel_bytes };
|
atlas_imgdata.subimage[0][0] = .{ data.data + header_size, atlas_pixel_bytes };
|
||||||
atlas_desc : sg_image_desc = .{
|
atlas_desc: sg_image_desc = .{
|
||||||
render_target = false,
|
render_target = false,
|
||||||
width = header.width,
|
width = header.width,
|
||||||
height = header.height,
|
height = header.height,
|
||||||
pixel_format = sg_pixel_format.RGBA32F,
|
pixel_format = sg_pixel_format.RGBA16F,
|
||||||
sample_count = 1,
|
sample_count = 1,
|
||||||
data = atlas_imgdata,
|
data = atlas_imgdata,
|
||||||
};
|
};
|
||||||
chunk_ptr := table_find_pointer(*curworld.world.chunks, req.chunk_key);
|
if g_rdm_atlas.id != 0 then sg_destroy_image(g_rdm_atlas);
|
||||||
lookup_path: string;
|
g_rdm_atlas = sg_make_image(*atlas_desc);
|
||||||
if chunk_ptr != null && chunk_ptr.rdm_lookup_path.count > 0 {
|
log_debug("RDM: loaded global atlas (%x%)", header.width, header.height);
|
||||||
lookup_path = sprint("%/worlds/%/%", GAME_RESOURCES_DIR, req.world_name, chunk_ptr.rdm_lookup_path);
|
|
||||||
} else {
|
|
||||||
lookup_path = rdm_chunk_filename(req.world_name, req.chunk_key, "rdm_lookup");
|
|
||||||
}
|
|
||||||
lookup_req : Fetch_Request;
|
|
||||||
lookup_req.type = .RDM_LOOKUP;
|
|
||||||
lookup_req.world_name = req.world_name;
|
|
||||||
lookup_req.chunk_key = req.chunk_key;
|
|
||||||
lookup_req.path = lookup_path;
|
|
||||||
lookup_req.rdm_pending_atlas = sg_make_image(*atlas_desc);
|
|
||||||
array_add(*g_asset_manager.rdm_queue, lookup_req);
|
|
||||||
|
|
||||||
case .RDM_LOOKUP;
|
case .RDM_MANIFEST;
|
||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
world_ok := curworld.valid && curworld.world.name == req.world_name;
|
if !curworld.valid || curworld.world.name != req.world_name then return;
|
||||||
|
|
||||||
if !world_ok {
|
json_str : string;
|
||||||
if req.rdm_pending_atlas.id != 0 then sg_destroy_image(req.rdm_pending_atlas);
|
json_str.data = data.data;
|
||||||
|
json_str.count = data.count;
|
||||||
|
ok, entries := Jaison.json_parse_string(json_str, [..]Rdm_Atlas_Entry,, temp);
|
||||||
|
if !ok {
|
||||||
|
log_error("RDM: failed to parse manifest for world '%'", req.world_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
header_size := cast(s64) size_of(RDM_File_Header);
|
array_reset_keeping_memory(*curworld.world.rdm_lookup);
|
||||||
if data.count < header_size {
|
for e: entries array_add(*curworld.world.rdm_lookup, e);
|
||||||
log_error("RDM: lookup too small for chunk %", req.chunk_key);
|
log_debug("RDM: loaded manifest (% entries)", entries.count);
|
||||||
sg_destroy_image(req.rdm_pending_atlas);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
header := cast(*RDM_File_Header) data.data;
|
|
||||||
if header.magic != RDM_FILE_MAGIC {
|
|
||||||
log_error("RDM: bad lookup magic for chunk %", req.chunk_key);
|
|
||||||
sg_destroy_image(req.rdm_pending_atlas);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lookup_pixel_bytes := cast(u64) header.width * cast(u64) header.height * 4 * size_of(float);
|
|
||||||
lookup_imgdata : sg_image_data;
|
|
||||||
lookup_imgdata.subimage[0][0] = .{ data.data + header_size, lookup_pixel_bytes };
|
|
||||||
lookup_desc : sg_image_desc = .{
|
|
||||||
render_target = false,
|
|
||||||
width = header.width,
|
|
||||||
height = header.height,
|
|
||||||
pixel_format = sg_pixel_format.RGBA32F,
|
|
||||||
sample_count = 1,
|
|
||||||
data = lookup_imgdata,
|
|
||||||
};
|
|
||||||
chunk := table_find_pointer(*curworld.world.chunks, req.chunk_key);
|
|
||||||
if chunk != null {
|
|
||||||
chunk.rdm_atlas = req.rdm_pending_atlas;
|
|
||||||
chunk.rdm_lookup = sg_make_image(*lookup_desc);
|
|
||||||
#if !FLAG_RELEASE_BUILD {
|
|
||||||
chunk.rdm_lookup_w = header.width;
|
|
||||||
chunk.rdm_lookup_h = header.height;
|
|
||||||
num_floats := cast(s64)(header.width * header.height * 4);
|
|
||||||
cpu_copy := NewArray(num_floats, float);
|
|
||||||
memcpy(cpu_copy.data, data.data + header_size, cast(s64)lookup_pixel_bytes);
|
|
||||||
chunk.rdm_lookup_cpu = cpu_copy;
|
|
||||||
}
|
|
||||||
chunk.rdm_valid = true;
|
|
||||||
log_debug("RDM: loaded chunk %", req.chunk_key);
|
|
||||||
} else {
|
|
||||||
sg_destroy_image(req.rdm_pending_atlas);
|
|
||||||
}
|
|
||||||
|
|
||||||
case .SHGRID;
|
case .SHGRID;
|
||||||
sh_loader_handle_completed(req, data);
|
sh_loader_handle_completed(req, data);
|
||||||
@ -384,15 +341,12 @@ add_resources_from_pack :: (pack: *Loaded_Pack) {
|
|||||||
array_add(*g_palettes, pal);
|
array_add(*g_palettes, pal);
|
||||||
log_info("Loaded % colors from hex palette(%)", pal.entries.count, v.name);
|
log_info("Loaded % colors from hex palette(%)", pal.entries.count, v.name);
|
||||||
case "ttf";
|
case "ttf";
|
||||||
// Load into a font. Add to free list.
|
|
||||||
case;
|
case;
|
||||||
log_warn("File(%) in pack(%) has unknown format", v.name, pack.name);
|
log_warn("File(%) in pack(%) has unknown format", v.name, pack.name);
|
||||||
}
|
}
|
||||||
log_debug("% -> %", v.name, extension);
|
log_debug("% -> %", v.name, extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properly initialize animations from the pack now that we have the images
|
|
||||||
// and the JSON files both combined.
|
|
||||||
for qsheet : sheets_to_init {
|
for qsheet : sheets_to_init {
|
||||||
for qsheet.sheet.meta.frameTags {
|
for qsheet.sheet.meta.frameTags {
|
||||||
anim : Animation;
|
anim : Animation;
|
||||||
@ -427,29 +381,51 @@ find_pack_by_name :: (name: string) -> (bool, Loaded_Pack) {
|
|||||||
return false, .{};
|
return false, .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_channel :: (queue: *[..]Fetch_Request, channel: u32) {
|
// Removes the highest-priority item from the queue (lowest enum value = most urgent).
|
||||||
af := *g_asset_manager.active[channel];
|
// Within equal priority, the earliest-queued item wins (stable).
|
||||||
if af.occupied || queue.count == 0 then return;
|
dequeue_highest_priority :: (queue: *[..]Fetch_Request) -> (Fetch_Request, bool) {
|
||||||
|
if queue.count == 0 return .{}, false;
|
||||||
req := queue.data[0];
|
best := 0;
|
||||||
for i: 0..queue.count - 2 {
|
for i: 1..queue.count - 1 {
|
||||||
queue.data[i] = queue.data[i + 1];
|
if queue.*[i].priority < queue.*[best].priority then best = i;
|
||||||
}
|
}
|
||||||
queue.count -= 1;
|
req := queue.*[best];
|
||||||
|
array_ordered_remove_by_index(queue, best);
|
||||||
|
return req, true;
|
||||||
|
}
|
||||||
|
|
||||||
af.occupied = true;
|
dispatch_slots :: () {
|
||||||
af.req = req;
|
for i: 0..MAX_FETCH_SLOTS - 1 {
|
||||||
af.accumulated.count = 0;
|
slot := *g_asset_manager.slots[i];
|
||||||
|
if slot.occupied continue;
|
||||||
|
if g_asset_manager.queue.count == 0 break;
|
||||||
|
|
||||||
path_c := to_c_string(req.path);
|
req, found := dequeue_highest_priority(*g_asset_manager.queue);
|
||||||
defer free(path_c);
|
if !found break;
|
||||||
sfetch_send(*(sfetch_request_t.{
|
|
||||||
channel = channel,
|
slot.occupied = true;
|
||||||
path = path_c,
|
slot.req = req;
|
||||||
callback = fetch_callback,
|
slot.buf = NewArray(fetch_type_buffer_size(req.type), u8, false);
|
||||||
chunk_size = cast(u32) af.chunk_buf.count,
|
|
||||||
buffer = .{ ptr = af.chunk_buf.data, size = cast(u64) af.chunk_buf.count },
|
// sokol copies user_data bytes immediately on sfetch_send, so a
|
||||||
}));
|
// stack-local index is safe here.
|
||||||
|
slot_idx : u32 = cast(u32) i;
|
||||||
|
|
||||||
|
// Route BACKGROUND requests to channel 1 so they don't starve
|
||||||
|
// LOADING_SCREEN / BLOCK_ENGINE work on native (two IO threads).
|
||||||
|
channel : u32 = ifx req.priority == .BACKGROUND then cast(u32)1 else cast(u32)0;
|
||||||
|
|
||||||
|
path_c := to_c_string(req.path);
|
||||||
|
defer free(path_c);
|
||||||
|
|
||||||
|
sfetch_send(*(sfetch_request_t.{
|
||||||
|
channel = channel,
|
||||||
|
path = path_c,
|
||||||
|
callback = fetch_callback,
|
||||||
|
buffer = .{ ptr = slot.buf.data, size = cast(u64) slot.buf.count },
|
||||||
|
user_data = .{ ptr = *slot_idx, size = cast(u64) size_of(u32) },
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#scope_export
|
#scope_export
|
||||||
@ -467,43 +443,35 @@ free_resources_from_pack :: (pack: *Loaded_Pack) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asset_manager_init :: () {
|
asset_manager_init :: () {
|
||||||
g_asset_manager.active[CHANNEL_MAIN].chunk_buf = NewArray(MAIN_CHUNK_BUFFER_SIZE, u8, false);
|
// No upfront allocations; buffers are allocated per-request in dispatch_slots.
|
||||||
g_asset_manager.active[CHANNEL_RDM].chunk_buf = NewArray(RDM_CHUNK_BUFFER_SIZE, u8, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mandatory_loads_done :: () -> bool {
|
mandatory_loads_done :: () -> bool {
|
||||||
for channel: 0..1 {
|
for g_asset_manager.slots {
|
||||||
af := *g_asset_manager.active[channel];
|
if it.occupied && it.req.priority == .BLOCK_ENGINE then return false;
|
||||||
if af.occupied && af.req.should_block_engine then return false;
|
|
||||||
}
|
}
|
||||||
for g_asset_manager.main_queue {
|
for g_asset_manager.queue {
|
||||||
if it.should_block_engine then return false;
|
if it.priority == .BLOCK_ENGINE then return false;
|
||||||
}
|
|
||||||
for g_asset_manager.rdm_queue {
|
|
||||||
if it.should_block_engine then return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
show_loading_screen :: () -> bool {
|
show_loading_screen :: () -> bool {
|
||||||
for channel: 0..1 {
|
for g_asset_manager.slots {
|
||||||
af := *g_asset_manager.active[channel];
|
if it.occupied && it.req.priority != .BACKGROUND then return true;
|
||||||
if af.occupied && af.req.should_block then return true;
|
|
||||||
}
|
}
|
||||||
for g_asset_manager.main_queue {
|
for g_asset_manager.queue {
|
||||||
if it.should_block then return true;
|
if it.priority != .BACKGROUND then return true;
|
||||||
}
|
|
||||||
for g_asset_manager.rdm_queue {
|
|
||||||
if it.should_block then return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
asset_manager_tick :: () {
|
asset_manager_tick :: () {
|
||||||
#if !FLAG_RELEASE_BUILD && OS != .WASM {
|
#if !FLAG_RELEASE_BUILD && OS != .WASM {
|
||||||
if g_asset_manager.pending_hot_reload
|
all_idle := true;
|
||||||
&& !g_asset_manager.active[CHANNEL_MAIN].occupied
|
for g_asset_manager.slots if it.occupied { all_idle = false; break; }
|
||||||
&& g_asset_manager.main_queue.count == 0 {
|
|
||||||
|
if g_asset_manager.pending_hot_reload && all_idle && g_asset_manager.queue.count == 0 {
|
||||||
g_asset_manager.pending_hot_reload = false;
|
g_asset_manager.pending_hot_reload = false;
|
||||||
hot_reload_all_packs();
|
hot_reload_all_packs();
|
||||||
}
|
}
|
||||||
@ -513,33 +481,30 @@ asset_manager_tick :: () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_channel(*g_asset_manager.main_queue, CHANNEL_MAIN);
|
|
||||||
dispatch_channel(*g_asset_manager.rdm_queue, CHANNEL_RDM);
|
|
||||||
|
|
||||||
sfetch_dowork();
|
sfetch_dowork();
|
||||||
|
dispatch_slots();
|
||||||
}
|
}
|
||||||
|
|
||||||
load_world :: (name: string) {
|
load_world :: (name: string) {
|
||||||
unload_current_world();
|
unload_current_world();
|
||||||
req : Fetch_Request;
|
req: Fetch_Request;
|
||||||
req.type = .WORLD;
|
req.type = .WORLD;
|
||||||
req.world_name = sprint("%", name);
|
req.priority = .LOADING_SCREEN;
|
||||||
req.path = sprint("%/worlds/%/world.json", GAME_RESOURCES_DIR, name);
|
req.world_name = sprint("%", name);
|
||||||
req.should_block = true;
|
req.path = sprint("%/worlds/%/world.json", GAME_RESOURCES_DIR, name);
|
||||||
array_add(*g_asset_manager.main_queue, req);
|
array_add(*g_asset_manager.queue, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
load_pack :: (name: string, shouldBlock: bool = true, shouldBlockEngine: bool = false) {
|
load_pack :: (name: string, priority: Load_Priority = .LOADING_SCREEN) {
|
||||||
req : Fetch_Request;
|
req: Fetch_Request;
|
||||||
req.type = .PACK;
|
req.type = .PACK;
|
||||||
req.pack_name = sprint("%", name);
|
req.priority = priority;
|
||||||
req.path = sprint("%/%.pack", PACK_DIR, name);
|
req.pack_name = sprint("%", name);
|
||||||
req.should_block = shouldBlock;
|
req.path = sprint("%/%.pack", PACK_DIR, name);
|
||||||
req.should_block_engine = shouldBlockEngine;
|
array_add(*g_asset_manager.queue, req);
|
||||||
array_add(*g_asset_manager.main_queue, req);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asset management:
|
// Asset accessors:
|
||||||
|
|
||||||
#scope_file
|
#scope_file
|
||||||
#scope_export
|
#scope_export
|
||||||
@ -549,7 +514,6 @@ get_texture_from_pack :: (pack: string, path: string) -> (sg_image) {
|
|||||||
invalid_img : sg_image;
|
invalid_img : sg_image;
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
// find_pack_by_name already logs this
|
|
||||||
return invalid_img;
|
return invalid_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,63 +1,58 @@
|
|||||||
// RDM streaming helpers.
|
// RDM streaming helpers.
|
||||||
// The unified fetch queue lives in asset_manager.jai; these functions
|
// The unified fetch queue lives in asset_manager.jai; these functions
|
||||||
// add and remove RDM entries from that shared queue.
|
// enqueue and cancel the world-level global RDM atlas + manifest.
|
||||||
|
|
||||||
#scope_export
|
#scope_export
|
||||||
|
|
||||||
rdm_loader_enqueue_world :: (world: *World) {
|
rdm_loader_enqueue_world :: (world: *World) {
|
||||||
for *chunk: world.chunks {
|
already_atlas := false;
|
||||||
if chunk.rdm_valid then continue;
|
already_manifest := false;
|
||||||
|
|
||||||
// Skip if this chunk is already in-flight on the RDM channel.
|
for g_asset_manager.slots {
|
||||||
af_rdm := *g_asset_manager.active[CHANNEL_RDM];
|
if !it.occupied continue;
|
||||||
if af_rdm.occupied {
|
if it.req.world_name != world.name continue;
|
||||||
cf := af_rdm.req;
|
if it.req.type == .RDM_ATLAS then already_atlas = true;
|
||||||
if (cf.type == .RDM_ATLAS || cf.type == .RDM_LOOKUP) &&
|
if it.req.type == .RDM_MANIFEST then already_manifest = true;
|
||||||
cf.world_name == world.name && cf.chunk_key == chunk.coord then continue;
|
}
|
||||||
}
|
for g_asset_manager.queue {
|
||||||
|
if it.world_name != world.name continue;
|
||||||
// Skip if already queued (either as atlas or its follow-up lookup).
|
if it.type == .RDM_ATLAS then already_atlas = true;
|
||||||
already_queued := false;
|
if it.type == .RDM_MANIFEST then already_manifest = true;
|
||||||
for g_asset_manager.rdm_queue {
|
}
|
||||||
if (it.type == .RDM_ATLAS || it.type == .RDM_LOOKUP) &&
|
|
||||||
it.world_name == world.name && it.chunk_key == chunk.coord {
|
|
||||||
already_queued = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if already_queued then continue;
|
|
||||||
|
|
||||||
|
if !already_atlas {
|
||||||
req : Fetch_Request;
|
req : Fetch_Request;
|
||||||
req.type = .RDM_ATLAS;
|
req.type = .RDM_ATLAS;
|
||||||
|
req.priority = .LOADING_SCREEN;
|
||||||
req.world_name = world.name;
|
req.world_name = world.name;
|
||||||
req.chunk_key = chunk.coord;
|
req.path = rdm_global_atlas_filename(world.name);
|
||||||
if chunk.rdm_atlas_path.count > 0 {
|
array_add(*g_asset_manager.queue, req);
|
||||||
req.path = sprint("%/worlds/%/%", GAME_RESOURCES_DIR, world.name, chunk.rdm_atlas_path);
|
}
|
||||||
} else {
|
if !already_manifest {
|
||||||
req.path = rdm_chunk_filename(world.name, chunk.coord, "rdm_atlas");
|
req : Fetch_Request;
|
||||||
}
|
req.type = .RDM_MANIFEST;
|
||||||
array_add(*g_asset_manager.rdm_queue, req);
|
req.priority = .LOADING_SCREEN;
|
||||||
|
req.world_name = world.name;
|
||||||
|
req.path = rdm_global_manifest_filename(world.name);
|
||||||
|
array_add(*g_asset_manager.queue, req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all pending RDM fetches from the queue and invalidate any in-flight
|
// Drain all pending RDM work.
|
||||||
// RDM fetch so its callback discards the result.
|
|
||||||
rdm_loader_cancel_all :: () {
|
rdm_loader_cancel_all :: () {
|
||||||
// Destroy any atlas images stored in queued RDM_LOOKUP requests, then drain.
|
new_count := 0;
|
||||||
for i: 0..g_asset_manager.rdm_queue.count - 1 {
|
for i: 0..g_asset_manager.queue.count - 1 {
|
||||||
item := g_asset_manager.rdm_queue[i];
|
item := g_asset_manager.queue[i];
|
||||||
if item.rdm_pending_atlas.id != 0 then sg_destroy_image(item.rdm_pending_atlas);
|
if item.type != .RDM_ATLAS && item.type != .RDM_MANIFEST {
|
||||||
}
|
g_asset_manager.queue[new_count] = item;
|
||||||
array_reset(*g_asset_manager.rdm_queue);
|
new_count += 1;
|
||||||
|
|
||||||
// Blank the world name on the in-flight RDM request so its callback discards.
|
|
||||||
af := *g_asset_manager.active[CHANNEL_RDM];
|
|
||||||
if af.occupied &&
|
|
||||||
(af.req.type == .RDM_ATLAS || af.req.type == .RDM_LOOKUP) {
|
|
||||||
if af.req.rdm_pending_atlas.id != 0 {
|
|
||||||
sg_destroy_image(af.req.rdm_pending_atlas);
|
|
||||||
af.req.rdm_pending_atlas = .{};
|
|
||||||
}
|
}
|
||||||
af.req.world_name = "";
|
}
|
||||||
|
g_asset_manager.queue.count = new_count;
|
||||||
|
|
||||||
|
for *slot: g_asset_manager.slots {
|
||||||
|
if !slot.occupied then continue;
|
||||||
|
if slot.req.type != .RDM_ATLAS && slot.req.type != .RDM_MANIFEST then continue;
|
||||||
|
slot.req.world_name = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,26 +8,33 @@ shgrid_loader_enqueue_world :: (world: *World) {
|
|||||||
for *chunk: world.chunks {
|
for *chunk: world.chunks {
|
||||||
if chunk.sh_valid then continue;
|
if chunk.sh_valid then continue;
|
||||||
|
|
||||||
af := *g_asset_manager.active[CHANNEL_RDM];
|
already := false;
|
||||||
if af.occupied && af.req.type == .SHGRID
|
for g_asset_manager.slots {
|
||||||
&& af.req.world_name == world.name && af.req.chunk_key == chunk.coord
|
if !it.occupied then continue;
|
||||||
then continue;
|
if it.req.type == .SHGRID &&
|
||||||
|
it.req.world_name == world.name && it.req.chunk_key == chunk.coord {
|
||||||
already_queued := false;
|
already = true;
|
||||||
for g_asset_manager.rdm_queue {
|
|
||||||
if it.type == .SHGRID && it.world_name == world.name && it.chunk_key == chunk.coord {
|
|
||||||
already_queued = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if already_queued then continue;
|
if already then continue;
|
||||||
|
|
||||||
|
for g_asset_manager.queue {
|
||||||
|
if it.type == .SHGRID &&
|
||||||
|
it.world_name == world.name && it.chunk_key == chunk.coord {
|
||||||
|
already = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if already then continue;
|
||||||
|
|
||||||
req : Fetch_Request;
|
req : Fetch_Request;
|
||||||
req.type = .SHGRID;
|
req.type = .SHGRID;
|
||||||
|
req.priority = .LOADING_SCREEN;
|
||||||
req.world_name = world.name;
|
req.world_name = world.name;
|
||||||
req.chunk_key = chunk.coord;
|
req.chunk_key = chunk.coord;
|
||||||
req.path = shgrid_filename(world.name, chunk.coord);
|
req.path = shgrid_filename(world.name, chunk.coord);
|
||||||
array_add(*g_asset_manager.rdm_queue, req);
|
array_add(*g_asset_manager.queue, req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,32 +57,40 @@ sh_loader_handle_completed :: (req: *Fetch_Request, data: []u8) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
n := sh_header.probe_n;
|
n := sh_header.probe_n;
|
||||||
expected_floats := cast(s64) n * n * n * 12;
|
total_values := cast(s64) n * n * n * 12;
|
||||||
if data.count < header_size + expected_floats * size_of(float) {
|
bytes_per_val := ifx sh_header.version >= 2 then size_of(u16) else size_of(float);
|
||||||
|
if data.count < header_size + total_values * bytes_per_val {
|
||||||
log_error("SH: probe grid data too short for chunk %", req.chunk_key);
|
log_error("SH: probe grid data too short for chunk %", req.chunk_key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack flat float[n^3 * 12] → 3-RGBA16F-texels-per-probe 2D texture layout.
|
// Pack flat [n^3 * 12] coefficients → 3-RGBA16F-texels-per-probe 2D texture layout.
|
||||||
// Texture dims: (n*3) x (n*n). Probe (px,py,pz) at row (pz*n+py), col (px*3+k).
|
// Texture dims: (n*3) x (n*n). Probe (px,py,pz) at row (pz*n+py), col (px*3+k).
|
||||||
// Coefficient layout in 3 texels (12 slots):
|
// Coefficient layout in 3 texels (12 slots):
|
||||||
// t0: R.c0-3 t1: G.c0-3 t2: B.c0-3
|
// t0: R.c0-3 t1: G.c0-3 t2: B.c0-3
|
||||||
tex_w := n * 3;
|
tex_w := n * 3;
|
||||||
n_tex := cast(s64) tex_w * n * n;
|
n_tex := cast(s64) tex_w * n * n;
|
||||||
packed := NewArray(n_tex * 4, u16);
|
packed := NewArray(n_tex * 4, u16);
|
||||||
src := cast(*float) (data.data + header_size);
|
if sh_header.version >= 2 {
|
||||||
for pz: 0..n-1 {
|
src := cast(*u16) (data.data + header_size);
|
||||||
for py: 0..n-1 {
|
for pz: 0..n-1 for py: 0..n-1 for px: 0..n-1 {
|
||||||
for px: 0..n-1 {
|
probe_idx := px + py * n + pz * n * n;
|
||||||
probe_idx := px + py * n + pz * n * n;
|
s := src + probe_idx * 12;
|
||||||
s := src + probe_idx * 12;
|
for k: 0..2 {
|
||||||
for k: 0..2 {
|
tex_idx := (pz * n + py) * tex_w + (px * 3 + k);
|
||||||
tex_idx := (pz * n + py) * tex_w + (px * 3 + k);
|
d := packed.data + tex_idx * 4;
|
||||||
d := packed.data + tex_idx * 4;
|
for ch: 0..3 d[ch] = (s + k * 4 + ch).*;
|
||||||
for ch: 0..3 {
|
}
|
||||||
d[ch] = f32_to_f16((s + k * 4 + ch).*);
|
}
|
||||||
}
|
} else {
|
||||||
}
|
src := cast(*float) (data.data + header_size);
|
||||||
|
for pz: 0..n-1 for py: 0..n-1 for px: 0..n-1 {
|
||||||
|
probe_idx := px + py * n + pz * n * n;
|
||||||
|
s := src + probe_idx * 12;
|
||||||
|
for k: 0..2 {
|
||||||
|
tex_idx := (pz * n + py) * tex_w + (px * 3 + k);
|
||||||
|
d := packed.data + tex_idx * 4;
|
||||||
|
for ch: 0..3 d[ch] = f32_to_f16((s + k * 4 + ch).*);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
#load "load.jai";
|
#load "load.jai";
|
||||||
#load "mixer.jai";
|
#load "mixer.jai";
|
||||||
|
|
||||||
|
g_audio_sample_rate : s32 = 44100;
|
||||||
|
|
||||||
audio_init :: () {
|
audio_init :: () {
|
||||||
// load_wav_file();
|
// load_wav_file();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,8 @@ Mixer_Play_Task :: struct {
|
|||||||
startTime : float64 = 0;
|
startTime : float64 = 0;
|
||||||
delay : float64 = 0;
|
delay : float64 = 0;
|
||||||
silenceBeforeRepeat : float64 = 0;
|
silenceBeforeRepeat : float64 = 0;
|
||||||
|
|
||||||
|
queued_next : *Mixer_Play_Task; // if set, starts when this ONESHOT task finishes
|
||||||
}
|
}
|
||||||
|
|
||||||
Mixer_Config :: struct {
|
Mixer_Config :: struct {
|
||||||
@ -71,6 +73,22 @@ mixer_add_task :: (audio: *Audio_Data, bus: Mixer_Bus, mode: Play_Mode) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Queue `next_task` to start as soon as the task currently playing `current_audio` finishes.
|
||||||
|
// If `current_audio` isn't playing, `next_task` starts immediately.
|
||||||
|
mixer_queue_next :: (current_audio: *Audio_Data, next_task: Mixer_Play_Task) {
|
||||||
|
mixer_lock(*g_mixer);
|
||||||
|
defer mixer_unlock(*g_mixer);
|
||||||
|
for *g_mixer.tasks {
|
||||||
|
if it.audio == current_audio {
|
||||||
|
if it.queued_next then free(it.queued_next);
|
||||||
|
it.queued_next = New(Mixer_Play_Task);
|
||||||
|
it.queued_next.* = next_task;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
array_add(*g_mixer.tasks, next_task);
|
||||||
|
}
|
||||||
|
|
||||||
mixer_get_samples :: (buffer: *float, frame_count: s32, channel_count: s32) {
|
mixer_get_samples :: (buffer: *float, frame_count: s32, channel_count: s32) {
|
||||||
mixer_lock(*g_mixer);
|
mixer_lock(*g_mixer);
|
||||||
defer mixer_unlock(*g_mixer);
|
defer mixer_unlock(*g_mixer);
|
||||||
@ -101,7 +119,7 @@ mixer_get_samples :: (buffer: *float, frame_count: s32, channel_count: s32) {
|
|||||||
source_data := task.audio.data;
|
source_data := task.audio.data;
|
||||||
source_channels := cast(s64) task.audio.channels;
|
source_channels := cast(s64) task.audio.channels;
|
||||||
src_rate := cast(s64) task.audio.sample_rate;
|
src_rate := cast(s64) task.audio.sample_rate;
|
||||||
out_rate := cast(s64) saudio_sample_rate();
|
out_rate := cast(s64) g_audio_sample_rate;
|
||||||
num_src_frames := source_data.count / source_channels;
|
num_src_frames := source_data.count / source_channels;
|
||||||
|
|
||||||
task_finished := false;
|
task_finished := false;
|
||||||
@ -145,7 +163,13 @@ mixer_get_samples :: (buffer: *float, frame_count: s32, channel_count: s32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if task_finished {
|
if task_finished {
|
||||||
array_unordered_remove_by_index(*g_mixer.tasks, i);
|
if task.queued_next {
|
||||||
|
next := task.queued_next.*;
|
||||||
|
free(task.queued_next);
|
||||||
|
g_mixer.tasks[i] = next;
|
||||||
|
} else {
|
||||||
|
array_unordered_remove_by_index(*g_mixer.tasks, i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,9 +79,7 @@ set_dist :: (dist: float) {
|
|||||||
|
|
||||||
tacomaSamples : s32 = 100;
|
tacomaSamples : s32 = 100;
|
||||||
tacomaResolution : s32 = 500;
|
tacomaResolution : s32 = 500;
|
||||||
tacomaExposure : float = 1.0;
|
tacoma_tab_scroll : float = 0;
|
||||||
tacomaContrast : float = 1.0;
|
|
||||||
tacomaSaturation : float = 1.0;
|
|
||||||
|
|
||||||
lastInputTime : float64;
|
lastInputTime : float64;
|
||||||
|
|
||||||
@ -237,33 +235,20 @@ tick_level_editor_camera :: () {
|
|||||||
|
|
||||||
draw_tacoma_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
draw_tacoma_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
r := total_r;
|
|
||||||
r.h = ui_h(3,0);
|
|
||||||
#if FLAG_TACOMA_ENABLED {
|
#if FLAG_TACOMA_ENABLED {
|
||||||
|
region, inside := GR.begin_scrollable_region(total_r, *theme.scrollable_region_theme);
|
||||||
|
r := inside;
|
||||||
|
r.y -= tacoma_tab_scroll;
|
||||||
|
r.h = ui_h(3,0);
|
||||||
if GR.button(r, "Render with Tacoma", *theme.button_theme) {
|
if GR.button(r, "Render with Tacoma", *theme.button_theme) {
|
||||||
cam := get_level_editor_camera();
|
cam := get_level_editor_camera();
|
||||||
gen_reference(tacomaResolution, tacomaResolution, cam.position, cam.target, tacomaSamples, true, curworld.world);
|
gen_reference(tacomaResolution, tacomaResolution, cam.position, cam.target, tacomaSamples, true, curworld.world);
|
||||||
}
|
}
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
if GR.button(r, "Render a RDM", *theme.button_theme) {
|
|
||||||
gen_rdm(tacomaSamples, true, curworld.world);
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
if GR.button(r, "Bake all chunk RDMs", *theme.button_theme) {
|
if GR.button(r, "Bake all chunk RDMs", *theme.button_theme) {
|
||||||
if curworld.valid then rdm_bake_all_chunks(curworld.world, tacomaSamples, true);
|
if curworld.valid then rdm_bake_all_chunks(curworld.world, tacomaSamples, true);
|
||||||
}
|
}
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
dirty_count := 0;
|
|
||||||
if curworld.valid for chunk: curworld.world.chunks if chunk.rdm_dirty dirty_count += 1;
|
|
||||||
if GR.button(r, tprint("Bake dirty chunks (%)", dirty_count), *t_button_selectable(theme, dirty_count > 0)) {
|
|
||||||
if curworld.valid && dirty_count > 0 {
|
|
||||||
dirty_keys : [..]Chunk_Key;
|
|
||||||
dirty_keys.allocator = temp;
|
|
||||||
for chunk, key: curworld.world.chunks if chunk.rdm_dirty array_add(*dirty_keys, key);
|
|
||||||
rdm_bake_chunks(dirty_keys, curworld.world, tacomaSamples, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
if GR.button(r, "Bake all chunk SH grids", *t_button_selectable(theme, !sh_bake.active)) {
|
if GR.button(r, "Bake all chunk SH grids", *t_button_selectable(theme, !sh_bake.active)) {
|
||||||
if curworld.valid && !sh_bake.active sh_bake_start(tacomaSamples);
|
if curworld.valid && !sh_bake.active sh_bake_start(tacomaSamples);
|
||||||
}
|
}
|
||||||
@ -303,18 +288,6 @@ draw_tacoma_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
|||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
GR.slider(r, *tacomaResolution, 10, 5000, 10, *theme.slider_theme);
|
GR.slider(r, *tacomaResolution, 10, 5000, 10, *theme.slider_theme);
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
GR.label(r, "Exposure", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
GR.slider(r, *tacomaExposure, 0.5, 3.0, 0.1, *theme.slider_theme);
|
|
||||||
r.y += r.h;
|
|
||||||
GR.label(r, "Contrast", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
GR.slider(r, *tacomaContrast, 0.5, 3.0, 0.1, *theme.slider_theme);
|
|
||||||
r.y += r.h;
|
|
||||||
GR.label(r, "Saturation", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
GR.slider(r, *tacomaSaturation, 0.5, 3.0, 0.1, *theme.slider_theme);
|
|
||||||
r.y += r.h;
|
|
||||||
GR.label(r, tprint("Sky Scale (bake): %", formatFloat(bake_sky_scale, trailing_width=2)), *t_label_left(theme));
|
GR.label(r, tprint("Sky Scale (bake): %", formatFloat(bake_sky_scale, trailing_width=2)), *t_label_left(theme));
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
GR.slider(r, *bake_sky_scale, 0.0, 5.0, 0.05, *theme.slider_theme);
|
GR.slider(r, *bake_sky_scale, 0.0, 5.0, 0.05, *theme.slider_theme);
|
||||||
@ -324,8 +297,10 @@ draw_tacoma_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
|||||||
GR.slider(r, *bake_sky_desaturation, 0.0, 1.0, 0.05, *theme.slider_theme);
|
GR.slider(r, *bake_sky_desaturation, 0.0, 1.0, 0.05, *theme.slider_theme);
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
|
|
||||||
|
GR.end_scrollable_region(region, r.x + r.w, r.y, *tacoma_tab_scroll);
|
||||||
} else {
|
} else {
|
||||||
|
r := total_r;
|
||||||
|
r.h = ui_h(3,0);
|
||||||
GR.label(r, "Tacoma is not enabled in this build.", *theme.label_theme);
|
GR.label(r, "Tacoma is not enabled in this build.", *theme.label_theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,7 +475,6 @@ add_trile :: (name: string, x: s32, y: s32, z: s32, orientation: u8 = 0) {
|
|||||||
group.trile_name = sprint("%", name);
|
group.trile_name = sprint("%", name);
|
||||||
array_add(*group.instances, inst);
|
array_add(*group.instances, inst);
|
||||||
array_add(*chunk.groups, group);
|
array_add(*chunk.groups, group);
|
||||||
chunk.rdm_dirty = true;
|
|
||||||
} @Command
|
} @Command
|
||||||
|
|
||||||
remove_trile :: (x: s32, y: s32, z: s32) {
|
remove_trile :: (x: s32, y: s32, z: s32) {
|
||||||
@ -516,7 +490,6 @@ remove_trile :: (x: s32, y: s32, z: s32) {
|
|||||||
for inst, idx: group.instances {
|
for inst, idx: group.instances {
|
||||||
if inst.x == lx && inst.y == ly && inst.z == lz {
|
if inst.x == lx && inst.y == ly && inst.z == lz {
|
||||||
array_unordered_remove_by_index(*group.instances, idx);
|
array_unordered_remove_by_index(*group.instances, idx);
|
||||||
chunk.rdm_dirty = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -540,14 +513,6 @@ tick_level_editor :: () {
|
|||||||
tick_level_editor_camera();
|
tick_level_editor_camera();
|
||||||
tick_particles(cast(float)delta_time);
|
tick_particles(cast(float)delta_time);
|
||||||
|
|
||||||
if is_action_start(Editor_Action.SAVE) then sworld();
|
|
||||||
|
|
||||||
if is_action_start(Editor_Action.LEVEL_TOOL_POINT) then current_tool_mode = .POINT;
|
|
||||||
if is_action_start(Editor_Action.LEVEL_TOOL_BRUSH) then current_tool_mode = .BRUSH;
|
|
||||||
if is_action_start(Editor_Action.LEVEL_TOOL_AREA) { current_tool_mode = .AREA; area_active = false; }
|
|
||||||
if is_action_start(Editor_Action.LEVEL_TOOL_LINE) { current_tool_mode = .LINE; line_active = false; }
|
|
||||||
if is_action_start(Editor_Action.LEVEL_TOOL_INSPECTOR) then current_tool_mode = .INSPECTOR;
|
|
||||||
if is_action_start(Editor_Action.LEVEL_TOOL_VIEWER) then current_tool_mode = .VIEWER;
|
|
||||||
if is_action_start(Editor_Action.LEVEL_TWIST_CCW) {
|
if is_action_start(Editor_Action.LEVEL_TWIST_CCW) {
|
||||||
lastInputTime = get_time();
|
lastInputTime = get_time();
|
||||||
current_orientation_twist = (current_orientation_twist + 1) % 4;
|
current_orientation_twist = (current_orientation_twist + 1) % 4;
|
||||||
@ -795,21 +760,35 @@ draw_inspector_panel :: (r: *GR.Rect, theme: *GR.Overall_Theme) {
|
|||||||
if has_trile {
|
if has_trile {
|
||||||
GR.label(r.*, tprint("Trile: % orient: %", trile_name, orientation), *t_label_left(theme));
|
GR.label(r.*, tprint("Trile: % orient: %", trile_name, orientation), *t_label_left(theme));
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
#if FLAG_TACOMA_ENABLED {
|
r.h = ui_h(3, 2);
|
||||||
r.h = ui_h(3, 2);
|
GR.label(r.*, "-- RDM --", *t_label_left(theme));
|
||||||
GR.label(r.*, "-- Bake Config --", *t_label_left(theme));
|
r.y += r.h;
|
||||||
|
r.h = ui_h(4, 0);
|
||||||
|
rdm_on := is_rdm_instance_enabled(world, inspector_x, inspector_y, inspector_z);
|
||||||
|
label := ifx rdm_on then "RDM: ON (sharp specular)" else "RDM: OFF (sky reflection)";
|
||||||
|
if GR.button(r.*, label, *theme.button_theme, 300) {
|
||||||
|
set_rdm_instance_enabled(world, inspector_x, inspector_y, inspector_z, !rdm_on);
|
||||||
|
}
|
||||||
|
r.y += r.h;
|
||||||
|
|
||||||
|
if rdm_on {
|
||||||
|
cur_size := get_rdm_instance_size(world, inspector_x, inspector_y, inspector_z);
|
||||||
|
tile_w := 2 * cur_size;
|
||||||
|
tile_h := 3 * cur_size;
|
||||||
|
size_label := tprint("Size: % (% x % px)", cur_size, tile_w, tile_h);
|
||||||
|
GR.label(r.*, size_label, *t_label_left(theme));
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
r.h = ui_h(4, 0);
|
|
||||||
size_ov, qual_ov := get_rdm_instance_override(world, inspector_x, inspector_y, inspector_z);
|
r2 := r.*;
|
||||||
GR.label(r.*, tprint("Size override (0=default): %", size_ov), *t_label_left(theme));
|
r2.w = r.w / 2;
|
||||||
|
if GR.button(r2, "- size", *theme.button_theme, 304) {
|
||||||
|
set_rdm_instance_size(world, inspector_x, inspector_y, inspector_z, rdm_cycle_size(cur_size, -1));
|
||||||
|
}
|
||||||
|
r2.x += r2.w;
|
||||||
|
if GR.button(r2, "+ size", *theme.button_theme, 305) {
|
||||||
|
set_rdm_instance_size(world, inspector_x, inspector_y, inspector_z, rdm_cycle_size(cur_size, +1));
|
||||||
|
}
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
GR.slider(r.*, *size_ov, 0, 512, 1, *theme.slider_theme);
|
|
||||||
r.y += r.h;
|
|
||||||
GR.label(r.*, tprint("Samples override (0=global): %", qual_ov), *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
GR.slider(r.*, *qual_ov, 0, 10000, 10, *theme.slider_theme);
|
|
||||||
r.y += r.h;
|
|
||||||
set_rdm_instance_override(world, inspector_x, inspector_y, inspector_z, size_ov, qual_ov);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GR.label(r.*, "Trile: (empty)", *t_label_left(theme));
|
GR.label(r.*, "Trile: (empty)", *t_label_left(theme));
|
||||||
@ -884,8 +863,9 @@ draw_inspector_panel :: (r: *GR.Rect, theme: *GR.Overall_Theme) {
|
|||||||
if has_note {
|
if has_note {
|
||||||
note := *world.notes[note_idx];
|
note := *world.notes[note_idx];
|
||||||
r.h = ui_h(4, 0);
|
r.h = ui_h(4, 0);
|
||||||
a, new_text, _ := GR.text_input(r.*, note.text, *theme.text_input_theme, 500, input_action = note_input_action);
|
note_id := cast(s64)inspector_x * 1000003 + cast(s64)inspector_y * 1009 + cast(s64)inspector_z;
|
||||||
if (a & .TEXT_MODIFIED) || (a & .ENTERED) note.text = copy_string(new_text);
|
a, _, note_state := GR.text_input(r.*, note.text, *theme.text_input_theme, note_id, input_action = note_input_action);
|
||||||
|
if (a & .TEXT_MODIFIED) || (a & .ENTERED) note.text = copy_string(note_state.text);
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
if GR.button(r.*, "Remove Note", *theme.button_theme, 302) {
|
if GR.button(r.*, "Remove Note", *theme.button_theme, 302) {
|
||||||
array_ordered_remove_by_index(*world.notes, note_idx);
|
array_ordered_remove_by_index(*world.notes, note_idx);
|
||||||
@ -908,77 +888,16 @@ draw_inspector_panel :: (r: *GR.Rect, theme: *GR.Overall_Theme) {
|
|||||||
GR.label(r.*, "-- RDM --", *t_label_left(theme));
|
GR.label(r.*, "-- RDM --", *t_label_left(theme));
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
|
|
||||||
chunk_key := world_to_chunk_coord(inspector_x, inspector_y, inspector_z);
|
rdm_flagged := is_rdm_instance_enabled(world, inspector_x, inspector_y, inspector_z);
|
||||||
chunk := table_find_pointer(*world.chunks, chunk_key);
|
if rdm_flagged {
|
||||||
if chunk != null && chunk.rdm_valid {
|
GR.label(r.*, "RDM: flagged", *t_label_left(theme));
|
||||||
GR.label(r.*, "RDM: baked", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
GR.label(r.*, tprint("Chunk: (%, %, %)", chunk_key.x, chunk_key.y, chunk_key.z), *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
|
|
||||||
roughness_mask : u8 = 0;
|
rect, has_rect := rdm_get_atlas_rect(world, inspector_x, inspector_y, inspector_z);
|
||||||
if has_trile {
|
if has_rect {
|
||||||
roughness_mask = get_trile_roughness_set(trile_name);
|
GR.label(r.*, tprint("Rect: %.2,%.2 %.2x%.2", rect.x, rect.y, rect.z, rect.w), *t_label_left(theme));
|
||||||
}
|
|
||||||
|
|
||||||
r.h = ui_h(4, 0);
|
|
||||||
GR.slider(r.*, *inspector_rdm_roughness, 0, 7, 1, *theme.slider_theme);
|
|
||||||
r.y += r.h;
|
|
||||||
r.h = ui_h(3, 2);
|
|
||||||
|
|
||||||
has_roughness := (roughness_mask & (1 << cast(u8)inspector_rdm_roughness)) != 0;
|
|
||||||
if has_roughness {
|
|
||||||
GR.label(r.*, tprint("Roughness %: present", inspector_rdm_roughness), *t_label_left(theme));
|
|
||||||
} else {
|
|
||||||
GR.label(r.*, tprint("Roughness %: not baked", inspector_rdm_roughness), *t_label_left(theme));
|
|
||||||
}
|
|
||||||
r.y += r.h;
|
|
||||||
|
|
||||||
available: String_Builder;
|
|
||||||
available.allocator = temp;
|
|
||||||
append(*available, "Available: ");
|
|
||||||
for i: 0..7 {
|
|
||||||
if roughness_mask & (1 << cast(u8)i) then print_to_builder(*available, "% ", i);
|
|
||||||
}
|
|
||||||
GR.label(r.*, builder_to_string(*available,, temp), *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
|
|
||||||
lx, ly, lz := world_to_local(inspector_x, inspector_y, inspector_z);
|
|
||||||
lookup_idx := cast(s32)lx + cast(s32)ly * 32 + cast(s32)lz * 1024 + cast(s32)inspector_rdm_roughness * 32768;
|
|
||||||
|
|
||||||
uv0 := Vector2.{0, 0};
|
|
||||||
uv1 := Vector2.{1, 0};
|
|
||||||
uv2 := Vector2.{1, 1};
|
|
||||||
uv3 := Vector2.{0, 1};
|
|
||||||
has_rect := false;
|
|
||||||
|
|
||||||
if chunk.rdm_lookup_cpu.data != null {
|
|
||||||
tx := lookup_idx % chunk.rdm_lookup_w;
|
|
||||||
ty := lookup_idx / chunk.rdm_lookup_w;
|
|
||||||
if tx >= 0 && ty >= 0 && tx < chunk.rdm_lookup_w && ty < chunk.rdm_lookup_h {
|
|
||||||
pixel_offset := (ty * chunk.rdm_lookup_w + tx) * 4;
|
|
||||||
rect_x := chunk.rdm_lookup_cpu[pixel_offset + 0];
|
|
||||||
rect_y := chunk.rdm_lookup_cpu[pixel_offset + 1];
|
|
||||||
rect_w := chunk.rdm_lookup_cpu[pixel_offset + 2];
|
|
||||||
rect_h := chunk.rdm_lookup_cpu[pixel_offset + 3];
|
|
||||||
if rect_w > 0 && rect_h > 0 {
|
|
||||||
has_rect = true;
|
|
||||||
fy0 := 1.0 - rect_y;
|
|
||||||
fy1 := 1.0 - (rect_y + rect_h);
|
|
||||||
uv0 = .{rect_x, fy0};
|
|
||||||
uv1 = .{rect_x + rect_w, fy0};
|
|
||||||
uv2 = .{rect_x + rect_w, fy1};
|
|
||||||
uv3 = .{rect_x, fy1};
|
|
||||||
GR.label(r.*, tprint("Rect: %.2,%.2 %.2x%.2", rect_x, rect_y, rect_w, rect_h), *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !has_rect {
|
|
||||||
GR.label(r.*, "No RDM entry for this slot/roughness", *t_label_left(theme));
|
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
} else {
|
|
||||||
r.y += r.h * 0.5;
|
r.y += r.h * 0.5;
|
||||||
tex_size := r.w * 0.9;
|
tex_size := r.w * 0.9;
|
||||||
tex_r : GR.Rect;
|
tex_r : GR.Rect;
|
||||||
@ -987,8 +906,15 @@ draw_inspector_panel :: (r: *GR.Rect, theme: *GR.Overall_Theme) {
|
|||||||
tex_r.w = tex_size;
|
tex_r.w = tex_size;
|
||||||
tex_r.h = tex_size * 1.5;
|
tex_r.h = tex_size * 1.5;
|
||||||
|
|
||||||
|
fy0 := 1.0 - rect.y;
|
||||||
|
fy1 := 1.0 - (rect.y + rect.w);
|
||||||
|
uv0 := Vector2.{rect.x, fy0};
|
||||||
|
uv1 := Vector2.{rect.x + rect.z, fy0};
|
||||||
|
uv2 := Vector2.{rect.x + rect.z, fy1};
|
||||||
|
uv3 := Vector2.{rect.x, fy1};
|
||||||
|
|
||||||
uiTex := New(Ui_Texture,, temp);
|
uiTex := New(Ui_Texture,, temp);
|
||||||
uiTex.tex = chunk.rdm_atlas;
|
uiTex.tex = g_rdm_atlas;
|
||||||
if uiTex.tex.id != INVALID_ID {
|
if uiTex.tex.id != INVALID_ID {
|
||||||
set_shader_for_images(uiTex);
|
set_shader_for_images(uiTex);
|
||||||
immediate_quad(
|
immediate_quad(
|
||||||
@ -1003,9 +929,12 @@ draw_inspector_panel :: (r: *GR.Rect, theme: *GR.Overall_Theme) {
|
|||||||
immediate_flush();
|
immediate_flush();
|
||||||
}
|
}
|
||||||
r.y += tex_r.h + r.h * 0.5;
|
r.y += tex_r.h + r.h * 0.5;
|
||||||
|
} else {
|
||||||
|
GR.label(r.*, "RDM: not yet baked", *t_label_left(theme));
|
||||||
|
r.y += r.h;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GR.label(r.*, "RDM: not baked", *t_label_left(theme));
|
GR.label(r.*, "RDM: not flagged", *t_label_left(theme));
|
||||||
r.y += r.h;
|
r.y += r.h;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1039,8 +968,9 @@ draw_level_editor :: () {
|
|||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
if !curworld.valid then return;
|
if !curworld.valid then return;
|
||||||
cam := get_level_editor_camera();
|
cam := get_level_editor_camera();
|
||||||
create_set_cam_rendering_task(cam, effective_plane_height(*curworld.world.conf));
|
ph := effective_plane_height(*curworld.world.conf);
|
||||||
create_world_rendering_tasks(*curworld.world, cam);
|
create_set_cam_rendering_task(cam, ph);
|
||||||
|
create_world_rendering_tasks(*curworld.world, cam, ph);
|
||||||
if show_trile_preview && !trile_preview_disabled {
|
if show_trile_preview && !trile_preview_disabled {
|
||||||
create_level_editor_preview_tasks();
|
create_level_editor_preview_tasks();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,7 +140,7 @@ draw_particle_editor_ui :: (theme: *GR.Overall_Theme) {
|
|||||||
}
|
}
|
||||||
er.y += er.h;
|
er.y += er.h;
|
||||||
|
|
||||||
pe_float(*er, tprint("Emission Rate: %", def.emission_rate), *def.emission_rate, 0, 200, 1, theme);
|
pe_float(*er, tprint("Emission Rate: %", def.emission_rate), *def.emission_rate, 0, 2000, 1, theme);
|
||||||
pe_float(*er, tprint("Lifetime Min: %", def.lifetime_min), *def.lifetime_min, 0, 10, 0.1, theme);
|
pe_float(*er, tprint("Lifetime Min: %", def.lifetime_min), *def.lifetime_min, 0, 10, 0.1, theme);
|
||||||
pe_float(*er, tprint("Lifetime Max: %", def.lifetime_max), *def.lifetime_max, 0, 10, 0.1, theme);
|
pe_float(*er, tprint("Lifetime Max: %", def.lifetime_max), *def.lifetime_max, 0, 10, 0.1, theme);
|
||||||
pe_vec3(*er, "Velocity", *def.velocity, -50, 50, theme);
|
pe_vec3(*er, "Velocity", *def.velocity, -50, 50, theme);
|
||||||
|
|||||||
@ -9,8 +9,12 @@ RDM_File_Header :: struct {
|
|||||||
|
|
||||||
RDM_FILE_MAGIC :: u32.[0x4D445254][0]; // "TRDM" as little-endian u32
|
RDM_FILE_MAGIC :: u32.[0x4D445254][0]; // "TRDM" as little-endian u32
|
||||||
|
|
||||||
rdm_chunk_filename :: (world_name: string, chunk_key: Chunk_Key, suffix: string) -> string {
|
rdm_global_atlas_filename :: (world_name: string) -> string {
|
||||||
return sprint("%/worlds/%/%_%_%.%", GAME_RESOURCES_DIR, world_name, chunk_key.x, chunk_key.y, chunk_key.z, suffix);
|
return sprint("%/worlds/%/rdm_atlas.rdm", GAME_RESOURCES_DIR, world_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
rdm_global_manifest_filename :: (world_name: string) -> string {
|
||||||
|
return sprint("%/worlds/%/rdm_manifest.json", GAME_RESOURCES_DIR, world_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SH probe grid (2 probes per trile per axis = 64x64x64 per 32x32x32 chunk)
|
// SH probe grid (2 probes per trile per axis = 64x64x64 per 32x32x32 chunk)
|
||||||
|
|||||||
@ -14,51 +14,39 @@ Tacoma_Screenshot :: struct {
|
|||||||
|
|
||||||
current_screenshot : Tacoma_Screenshot;
|
current_screenshot : Tacoma_Screenshot;
|
||||||
|
|
||||||
RDM_Atlas_Entry :: struct {
|
// Single global atlas entry: stored on the World once baked.
|
||||||
|
RDM_Atlas_Entry_Bake :: struct {
|
||||||
world_pos: Vector3;
|
world_pos: Vector3;
|
||||||
roughness: s32;
|
x, y: s32; // top-left in atlas pixels
|
||||||
x, y: s32; // position in atlas (pixels)
|
w, h: s32; // pixel size of this RDM (= 2*size, 3*size)
|
||||||
w, h: s32; // size of this RDM
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-chunk atlas bake state (CPU-side, used during baking only).
|
RDM_ATLAS_MAX_SIZE :: 16384; // GPU 2D texture ceiling.
|
||||||
RDM_Chunk_Bake :: struct {
|
|
||||||
data: *float; // CPU atlas buffer (RGBA32F)
|
|
||||||
width: s32;
|
|
||||||
height: s32;
|
|
||||||
cursor_x: s32;
|
|
||||||
cursor_y: s32;
|
|
||||||
row_height: s32;
|
|
||||||
entries: [..]RDM_Atlas_Entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
RDM_ATLAS_SIZE :: 4096 * 2;
|
|
||||||
RDM_LOOKUP_SIZE :: 512; // 512x512 = 32*32*32*8 = 262144 texels
|
|
||||||
|
|
||||||
rdm_chunk_bakes : Table(Chunk_Key, RDM_Chunk_Bake, chunk_key_hash, chunk_key_compare);
|
|
||||||
|
|
||||||
ctx : *Tacoma.Tacoma_Context;
|
ctx : *Tacoma.Tacoma_Context;
|
||||||
|
|
||||||
// --- Chunk RDM bake queue ---
|
|
||||||
|
|
||||||
// Default RDM sizes per roughness level (0=sharpest, 7=diffuse).
|
|
||||||
// w = 2*size, h = 3*size. Tacoma accepts size directly.
|
|
||||||
g_rdm_default_sizes : [8]s32 = .[256, 128, 64, 32, 16, 8, 4, 2];
|
|
||||||
|
|
||||||
// A single RDM render job: one world_trile at one roughness level.
|
|
||||||
RDM_Bake_Job :: struct {
|
RDM_Bake_Job :: struct {
|
||||||
world_trile_index: s32;
|
world_trile_index: s32;
|
||||||
roughness: s32;
|
|
||||||
world_pos: Vector3;
|
world_pos: Vector3;
|
||||||
size_override: s32; // 0 = use g_rdm_default_sizes[roughness]
|
size: s32; // per-instance RDM side in px (tile = 2*size x 3*size).
|
||||||
quality_override: s32; // 0 = use rdm_bake.quality
|
}
|
||||||
|
|
||||||
|
RDM_Global_Bake :: struct {
|
||||||
|
data : *float; // CPU atlas buffer (RGBA32F during bake)
|
||||||
|
width : s32;
|
||||||
|
height : s32;
|
||||||
|
cursor_x : s32;
|
||||||
|
cursor_y : s32;
|
||||||
|
row_height : s32;
|
||||||
|
entries : [..]RDM_Atlas_Entry_Bake;
|
||||||
}
|
}
|
||||||
|
|
||||||
RDM_Bake_State :: struct {
|
RDM_Bake_State :: struct {
|
||||||
active : bool = false;
|
active : bool = false;
|
||||||
quality : s32 = 100;
|
quality : s32 = 100;
|
||||||
jobs : [..]RDM_Bake_Job;
|
jobs : [..]RDM_Bake_Job;
|
||||||
current_job : s32 = 0;
|
current_job : s32 = 0;
|
||||||
|
atlas : RDM_Global_Bake;
|
||||||
}
|
}
|
||||||
|
|
||||||
rdm_bake : RDM_Bake_State;
|
rdm_bake : RDM_Bake_State;
|
||||||
@ -73,72 +61,62 @@ SH_Bake_State :: struct {
|
|||||||
|
|
||||||
sh_bake : SH_Bake_State;
|
sh_bake : SH_Bake_State;
|
||||||
|
|
||||||
rdm_job_size :: (job: RDM_Bake_Job) -> s32 {
|
|
||||||
if job.size_override > 0 then return job.size_override;
|
|
||||||
return g_rdm_default_sizes[job.roughness];
|
|
||||||
}
|
|
||||||
|
|
||||||
rdm_job_quality :: (job: RDM_Bake_Job) -> s32 {
|
rdm_job_quality :: (job: RDM_Bake_Job) -> s32 {
|
||||||
if job.quality_override > 0 then return job.quality_override;
|
|
||||||
return rdm_bake.quality;
|
return rdm_bake.quality;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pixel dimensions of a single RDM entry. w = 2*size, h = 3*size.
|
rdm_sort_jobs_by_height_desc :: (jobs: *[..]RDM_Bake_Job) {
|
||||||
rdm_entry_predicted_size :: (roughness: s32) -> (w: s32, h: s32) {
|
n := jobs.count;
|
||||||
size := g_rdm_default_sizes[roughness];
|
for i: 1..n-1 {
|
||||||
return 2 * size, 3 * size;
|
key := jobs.data[i];
|
||||||
|
j := i - 1;
|
||||||
|
while j >= 0 && jobs.data[j].size < key.size {
|
||||||
|
jobs.data[j + 1] = jobs.data[j];
|
||||||
|
j -= 1;
|
||||||
|
}
|
||||||
|
jobs.data[j + 1] = key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate shelf-pack of jobs into a canvas of given width; return total height used.
|
// Simulate shelf pack of the current jobs array into a square atlas of `atlas_size`.
|
||||||
rdm_simulate_pack_height :: (jobs: []RDM_Bake_Job, canvas_w: s32) -> s32 {
|
// Returns true if all fit. Assumes jobs are already sorted by tile height descending.
|
||||||
cx : s32 = 0;
|
rdm_simulate_shelf_pack :: (jobs: []RDM_Bake_Job, atlas_size: s32) -> bool {
|
||||||
cy : s32 = 0;
|
cursor_x : s32 = 0;
|
||||||
rh : s32 = 0;
|
cursor_y : s32 = 0;
|
||||||
|
row_h : s32 = 0;
|
||||||
for job: jobs {
|
for job: jobs {
|
||||||
s := rdm_job_size(job);
|
w := 2 * job.size;
|
||||||
w := 2 * s;
|
h := 3 * job.size;
|
||||||
h := 3 * s;
|
if w > atlas_size || h > atlas_size return false;
|
||||||
if cx + w > canvas_w { cy += rh; cx = 0; rh = 0; }
|
if cursor_x + w > atlas_size {
|
||||||
cx += w;
|
cursor_y += row_h;
|
||||||
if h > rh rh = h;
|
cursor_x = 0;
|
||||||
|
row_h = 0;
|
||||||
|
}
|
||||||
|
if cursor_y + h > atlas_size return false;
|
||||||
|
cursor_x += w;
|
||||||
|
if h > row_h then row_h = h;
|
||||||
}
|
}
|
||||||
cy += rh;
|
return true;
|
||||||
return cy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate shelf-packing for a chunk's sorted job list and return minimum
|
// Choose min pow2 square atlas fitting all jobs (variable tile sizes). Caps at RDM_ATLAS_MAX_SIZE.
|
||||||
// power-of-2 atlas dimensions, capped at RDM_ATLAS_SIZE.
|
rdm_calc_global_atlas_size :: (jobs: []RDM_Bake_Job) -> (s32, s32) {
|
||||||
rdm_calc_chunk_atlas_size :: (jobs: []RDM_Bake_Job) -> (s32, s32) {
|
if jobs.count == 0 return 16, 16;
|
||||||
if jobs.count == 0 return 16, 16;
|
target : s32 = 16;
|
||||||
|
while target < RDM_ATLAS_MAX_SIZE {
|
||||||
// Estimate canvas width from sqrt(total pixel area).
|
if rdm_simulate_shelf_pack(jobs, target) return target, target;
|
||||||
total_area : s64 = 0;
|
target *= 2;
|
||||||
for job: jobs {
|
|
||||||
s := rdm_job_size(job);
|
|
||||||
total_area += cast(s64)(2 * s) * cast(s64)(3 * s);
|
|
||||||
}
|
}
|
||||||
target_w : s32 = 16;
|
if !rdm_simulate_shelf_pack(jobs, RDM_ATLAS_MAX_SIZE) {
|
||||||
sqrt_area := cast(s32) sqrt(cast(float) total_area) + 1;
|
log_warn("RDM atlas cap reached (%); some instances may not fit.", RDM_ATLAS_MAX_SIZE);
|
||||||
while target_w < sqrt_area target_w *= 2;
|
|
||||||
|
|
||||||
// Widen until the packed height fits within the width (roughly square).
|
|
||||||
while target_w < RDM_ATLAS_SIZE {
|
|
||||||
if rdm_simulate_pack_height(jobs, target_w) <= target_w break;
|
|
||||||
target_w *= 2;
|
|
||||||
}
|
}
|
||||||
|
return RDM_ATLAS_MAX_SIZE, RDM_ATLAS_MAX_SIZE;
|
||||||
// Round height up to next power of 2.
|
|
||||||
final_height := rdm_simulate_pack_height(jobs, target_w);
|
|
||||||
target_h : s32 = 16;
|
|
||||||
while target_h < final_height target_h *= 2;
|
|
||||||
|
|
||||||
if target_w > RDM_ATLAS_SIZE target_w = RDM_ATLAS_SIZE;
|
|
||||||
if target_h > RDM_ATLAS_SIZE target_h = RDM_ATLAS_SIZE;
|
|
||||||
return target_w, target_h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the Tacoma scene from the world, and emit bake jobs for selected chunks.
|
// Build the Tacoma scene from the world and emit one bake job per RDM-flagged instance.
|
||||||
// If chunk_keys is null, all chunks are queued.
|
// chunk_keys is accepted for API compatibility but ignored — bakes always cover every flagged
|
||||||
|
// instance because the global atlas + manifest is rewritten as a whole.
|
||||||
rdm_bake_start :: (world: World, quality: s32, include_water: bool, chunk_keys: []Chunk_Key = .[]) {
|
rdm_bake_start :: (world: World, quality: s32, include_water: bool, chunk_keys: []Chunk_Key = .[]) {
|
||||||
if rdm_bake.active then return;
|
if rdm_bake.active then return;
|
||||||
|
|
||||||
@ -150,27 +128,7 @@ rdm_bake_start :: (world: World, quality: s32, include_water: bool, chunk_keys:
|
|||||||
trile_name_to_index: Table(string, s32);
|
trile_name_to_index: Table(string, s32);
|
||||||
trile_name_to_index.allocator = temp;
|
trile_name_to_index.allocator = temp;
|
||||||
|
|
||||||
// Per world_trile: which roughnesses to bake.
|
|
||||||
world_trile_roughnesses : [..]u8;
|
|
||||||
world_trile_roughnesses.allocator = temp;
|
|
||||||
|
|
||||||
// Cache roughness sets per trile type.
|
|
||||||
roughness_cache: Table(string, u8);
|
|
||||||
roughness_cache.allocator = temp;
|
|
||||||
|
|
||||||
bake_all := chunk_keys.count == 0;
|
|
||||||
|
|
||||||
// Build a set of chunk keys to bake if specific ones were requested.
|
|
||||||
chunk_key_set: Table(Chunk_Key, bool, chunk_key_hash, chunk_key_compare);
|
|
||||||
chunk_key_set.allocator = temp;
|
|
||||||
if !bake_all {
|
|
||||||
for key: chunk_keys {
|
|
||||||
table_set(*chunk_key_set, key, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for chunk: world.chunks {
|
for chunk: world.chunks {
|
||||||
should_bake := bake_all || table_contains(*chunk_key_set, chunk.coord);
|
|
||||||
for group: chunk.groups {
|
for group: chunk.groups {
|
||||||
success, idx := table_find(*trile_name_to_index, group.trile_name);
|
success, idx := table_find(*trile_name_to_index, group.trile_name);
|
||||||
if !success {
|
if !success {
|
||||||
@ -196,36 +154,19 @@ rdm_bake_start :: (world: World, quality: s32, include_water: bool, chunk_keys:
|
|||||||
table_set(*trile_name_to_index, group.trile_name, idx);
|
table_set(*trile_name_to_index, group.trile_name, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get roughness set for this trile type (cached).
|
|
||||||
roughness_mask : u8;
|
|
||||||
found_r, cached_mask := table_find(*roughness_cache, group.trile_name);
|
|
||||||
if found_r {
|
|
||||||
roughness_mask = cached_mask;
|
|
||||||
} else {
|
|
||||||
roughness_mask = get_trile_roughness_set(group.trile_name);
|
|
||||||
table_set(*roughness_cache, group.trile_name, roughness_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
for inst: group.instances {
|
for inst: group.instances {
|
||||||
world_trile_idx := cast(s32) world_triles.count;
|
world_trile_idx := cast(s32) world_triles.count;
|
||||||
wx, wy, wz := chunk_local_to_world(chunk.coord, inst.x, inst.y, inst.z);
|
wx, wy, wz := chunk_local_to_world(chunk.coord, inst.x, inst.y, inst.z);
|
||||||
wpos := Vector3.{cast(float) wx, cast(float) wy, cast(float) wz};
|
wpos := Vector3.{cast(float) wx, cast(float) wy, cast(float) wz};
|
||||||
array_add(*world_triles, Tacoma.World_Trile.{idx, wpos, cast(s32) inst.orientation});
|
array_add(*world_triles, Tacoma.World_Trile.{idx, wpos, cast(s32) inst.orientation});
|
||||||
array_add(*world_trile_roughnesses, roughness_mask);
|
|
||||||
|
|
||||||
if should_bake {
|
inst_size := get_rdm_instance_size(*world, wx, wy, wz);
|
||||||
size_ov, qual_ov := get_rdm_instance_override(*world, wx, wy, wz);
|
if inst_size > 0 {
|
||||||
for r: 0..7 {
|
array_add(*rdm_bake.jobs, .{
|
||||||
if roughness_mask & cast(u8)(1 << r) {
|
world_trile_index = world_trile_idx,
|
||||||
array_add(*rdm_bake.jobs, .{
|
world_pos = wpos,
|
||||||
world_trile_index = world_trile_idx,
|
size = inst_size,
|
||||||
roughness = cast(s32) r,
|
});
|
||||||
world_pos = wpos,
|
|
||||||
size_override = size_ov,
|
|
||||||
quality_override = qual_ov,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,69 +178,18 @@ rdm_bake_start :: (world: World, quality: s32, include_water: bool, chunk_keys:
|
|||||||
ctx = Tacoma.tacoma_init("./modules/Tacoma/");
|
ctx = Tacoma.tacoma_init("./modules/Tacoma/");
|
||||||
Tacoma.tacoma_load_scene(ctx, sky, blases, tlas, cast(s32) include_water);
|
Tacoma.tacoma_load_scene(ctx, sky, blases, tlas, cast(s32) include_water);
|
||||||
|
|
||||||
// Sort jobs by roughness ascending (lowest roughness = biggest images first).
|
// Sort jobs tallest-first for a tighter shelf pack.
|
||||||
// Simple insertion sort, N is small.
|
rdm_sort_jobs_by_height_desc(*rdm_bake.jobs);
|
||||||
if rdm_bake.jobs.count > 1 {
|
|
||||||
for i: 1..cast(s32)(rdm_bake.jobs.count - 1) {
|
|
||||||
key := rdm_bake.jobs[i];
|
|
||||||
j := i - 1;
|
|
||||||
while j >= 0 && rdm_bake.jobs[j].roughness > key.roughness {
|
|
||||||
rdm_bake.jobs[j + 1] = rdm_bake.jobs[j];
|
|
||||||
j -= 1;
|
|
||||||
}
|
|
||||||
rdm_bake.jobs[j + 1] = key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any previous per-chunk bake data.
|
// Allocate global CPU atlas large enough for all jobs.
|
||||||
rdm_cleanup_chunk_bakes();
|
aw, ah := rdm_calc_global_atlas_size(rdm_bake.jobs);
|
||||||
|
atlas_bytes := cast(s64) aw * cast(s64) ah * 4 * size_of(float);
|
||||||
// Clear RDM results only for chunks that are being re-baked.
|
rdm_bake.atlas.width = aw;
|
||||||
curworld := get_current_world();
|
rdm_bake.atlas.height = ah;
|
||||||
for *chunk: curworld.world.chunks {
|
rdm_bake.atlas.data = cast(*float) alloc(atlas_bytes);
|
||||||
if chunk.rdm_valid && (bake_all || table_contains(*chunk_key_set, chunk.coord)) {
|
memset(rdm_bake.atlas.data, 0, atlas_bytes);
|
||||||
sg_destroy_image(chunk.rdm_atlas);
|
log_info("RDM global atlas: %x% (% MB) for % jobs",
|
||||||
sg_destroy_image(chunk.rdm_lookup);
|
aw, ah, cast(float)atlas_bytes / (1024.0 * 1024.0), rdm_bake.jobs.count);
|
||||||
chunk.rdm_valid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-allocate per-chunk atlas CPU buffers with optimal (minimum) sizes.
|
|
||||||
{
|
|
||||||
// Collect unique chunk keys.
|
|
||||||
unique_chunk_keys : [..]Chunk_Key;
|
|
||||||
unique_chunk_keys.allocator = temp;
|
|
||||||
for job: rdm_bake.jobs {
|
|
||||||
ck := world_to_chunk_coord(cast(s32) job.world_pos.x, cast(s32) job.world_pos.y, cast(s32) job.world_pos.z);
|
|
||||||
already := false;
|
|
||||||
for unique_chunk_keys { if it == ck { already = true; break; } }
|
|
||||||
if !already array_add(*unique_chunk_keys, ck);
|
|
||||||
}
|
|
||||||
|
|
||||||
for chunk_key: unique_chunk_keys {
|
|
||||||
// Collect this chunk's jobs (already sorted by roughness ascending).
|
|
||||||
this_jobs : [..]RDM_Bake_Job;
|
|
||||||
this_jobs.allocator = temp;
|
|
||||||
for job: rdm_bake.jobs {
|
|
||||||
ck := world_to_chunk_coord(cast(s32) job.world_pos.x, cast(s32) job.world_pos.y, cast(s32) job.world_pos.z);
|
|
||||||
if ck == chunk_key array_add(*this_jobs, job);
|
|
||||||
}
|
|
||||||
|
|
||||||
atlas_w, atlas_h := rdm_calc_chunk_atlas_size(this_jobs);
|
|
||||||
log_info("RDM atlas for chunk %: %x% (%.1f MB, was % MB)",
|
|
||||||
chunk_key, atlas_w, atlas_h,
|
|
||||||
cast(float)(cast(s64) atlas_w * atlas_h * 4 * size_of(float)) / (1024.0 * 1024.0),
|
|
||||||
RDM_ATLAS_SIZE * RDM_ATLAS_SIZE * 4 * size_of(float) / (1024 * 1024));
|
|
||||||
|
|
||||||
atlas_bytes := cast(s64) atlas_w * cast(s64) atlas_h * 4 * size_of(float);
|
|
||||||
bake : RDM_Chunk_Bake;
|
|
||||||
bake.width = atlas_w;
|
|
||||||
bake.height = atlas_h;
|
|
||||||
bake.data = cast(*float) alloc(atlas_bytes);
|
|
||||||
memset(bake.data, 0, atlas_bytes);
|
|
||||||
table_set(*rdm_chunk_bakes, chunk_key, bake);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rdm_bake.active = true;
|
rdm_bake.active = true;
|
||||||
rdm_bake.quality = quality;
|
rdm_bake.quality = quality;
|
||||||
@ -309,17 +199,16 @@ rdm_bake_start :: (world: World, quality: s32, include_water: bool, chunk_keys:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue all chunks for RDM baking.
|
// Queue all RDM-flagged instances. (chunk_keys parameter is ignored — see rdm_bake_start.)
|
||||||
rdm_bake_all_chunks :: (world: World, quality: s32, include_water: bool) {
|
rdm_bake_all_chunks :: (world: World, quality: s32, include_water: bool) {
|
||||||
rdm_bake_start(world, quality, include_water);
|
rdm_bake_start(world, quality, include_water);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue specific chunks for RDM baking.
|
|
||||||
rdm_bake_chunks :: (chunk_keys: []Chunk_Key, world: World, quality: s32, include_water: bool) {
|
rdm_bake_chunks :: (chunk_keys: []Chunk_Key, world: World, quality: s32, include_water: bool) {
|
||||||
rdm_bake_start(world, quality, include_water, chunk_keys);
|
rdm_bake_start(world, quality, include_water, chunk_keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called once per frame to process at most one RDM.
|
// Process at most one RDM per frame.
|
||||||
rdm_bake_tick :: () {
|
rdm_bake_tick :: () {
|
||||||
if !rdm_bake.active then return;
|
if !rdm_bake.active then return;
|
||||||
if rdm_bake.current_job >= cast(s32) rdm_bake.jobs.count {
|
if rdm_bake.current_job >= cast(s32) rdm_bake.jobs.count {
|
||||||
@ -329,55 +218,46 @@ rdm_bake_tick :: () {
|
|||||||
|
|
||||||
job := rdm_bake.jobs[rdm_bake.current_job];
|
job := rdm_bake.jobs[rdm_bake.current_job];
|
||||||
|
|
||||||
size := rdm_job_size(job);
|
size : s32 = job.size;
|
||||||
w := 2 * size;
|
w := 2 * size;
|
||||||
h := 3 * size;
|
h := 3 * size;
|
||||||
ptr := Tacoma.tacoma_render_rdm(ctx, job.world_trile_index, job.roughness, rdm_job_quality(job), size);
|
ptr := Tacoma.tacoma_render_rdm(ctx, job.world_trile_index, 0, rdm_job_quality(job), size);
|
||||||
|
|
||||||
// Find this job's per-chunk bake state.
|
bake := *rdm_bake.atlas;
|
||||||
chunk_key := world_to_chunk_coord(cast(s32) job.world_pos.x, cast(s32) job.world_pos.y, cast(s32) job.world_pos.z);
|
|
||||||
bake := table_find_pointer(*rdm_chunk_bakes, chunk_key);
|
|
||||||
|
|
||||||
if bake != null {
|
if bake.cursor_x + w > bake.width {
|
||||||
// Shelf-pack this RDM into the chunk's atlas.
|
bake.cursor_y += bake.row_height;
|
||||||
if bake.cursor_x + w > bake.width {
|
bake.cursor_x = 0;
|
||||||
bake.cursor_y += bake.row_height;
|
bake.row_height = 0;
|
||||||
bake.cursor_x = 0;
|
|
||||||
bake.row_height = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ax := bake.cursor_x;
|
|
||||||
ay := bake.cursor_y;
|
|
||||||
|
|
||||||
if ay + h <= bake.height {
|
|
||||||
// Copy pixels row-by-row into the chunk's atlas CPU buffer.
|
|
||||||
src := cast(*u8) ptr;
|
|
||||||
row_bytes := cast(s64) w * 4 * size_of(float);
|
|
||||||
for row: 0..h-1 {
|
|
||||||
dst_offset := (cast(s64)(ay + row) * cast(s64) bake.width + cast(s64) ax) * 4 * size_of(float);
|
|
||||||
src_offset := cast(s64) row * row_bytes;
|
|
||||||
memcpy(cast(*u8) bake.data + dst_offset, src + src_offset, row_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
entry : RDM_Atlas_Entry;
|
|
||||||
entry.world_pos = job.world_pos;
|
|
||||||
entry.roughness = job.roughness;
|
|
||||||
entry.x = ax;
|
|
||||||
entry.y = ay;
|
|
||||||
entry.w = w;
|
|
||||||
entry.h = h;
|
|
||||||
array_add(*bake.entries, entry);
|
|
||||||
} else {
|
|
||||||
log_warn("RDM atlas overflow for chunk %, skipping (pos=%, roughness=%)", chunk_key, job.world_pos, job.roughness);
|
|
||||||
}
|
|
||||||
|
|
||||||
bake.cursor_x += w;
|
|
||||||
if h > bake.row_height then bake.row_height = h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Still update the preview screenshot.
|
ax := bake.cursor_x;
|
||||||
tacoma_handle_result(ptr, w, h);
|
ay := bake.cursor_y;
|
||||||
|
|
||||||
|
if ay + h <= bake.height {
|
||||||
|
src := cast(*u8) ptr;
|
||||||
|
row_bytes := cast(s64) w * 4 * size_of(float);
|
||||||
|
for row: 0..h-1 {
|
||||||
|
dst_offset := (cast(s64)(ay + row) * cast(s64) bake.width + cast(s64) ax) * 4 * size_of(float);
|
||||||
|
src_offset := cast(s64) row * row_bytes;
|
||||||
|
memcpy(cast(*u8) bake.data + dst_offset, src + src_offset, row_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry : RDM_Atlas_Entry_Bake;
|
||||||
|
entry.world_pos = job.world_pos;
|
||||||
|
entry.x = ax;
|
||||||
|
entry.y = ay;
|
||||||
|
entry.w = w;
|
||||||
|
entry.h = h;
|
||||||
|
array_add(*bake.entries, entry);
|
||||||
|
} else {
|
||||||
|
log_warn("RDM global atlas overflow, skipping (pos=%)", job.world_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
bake.cursor_x += w;
|
||||||
|
if h > bake.row_height then bake.row_height = h;
|
||||||
|
|
||||||
|
tacoma_handle_result(ptr, w, h);
|
||||||
rdm_bake.current_job += 1;
|
rdm_bake.current_job += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,115 +265,68 @@ rdm_bake_finish :: () {
|
|||||||
if ctx != null then tacoma_stop();
|
if ctx != null then tacoma_stop();
|
||||||
|
|
||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
total_entries : s64 = 0;
|
bake := *rdm_bake.atlas;
|
||||||
chunk_count : s64 = 0;
|
|
||||||
|
|
||||||
// Collect unique chunk keys from jobs.
|
if bake.entries.count > 0 {
|
||||||
bake_chunk_keys : [..]Chunk_Key;
|
// a) Pack atlas into RGBA16F half-floats and upload to g_rdm_atlas.
|
||||||
bake_chunk_keys.allocator = temp;
|
upload_global_atlas_image(bake);
|
||||||
for job: rdm_bake.jobs {
|
|
||||||
ck := world_to_chunk_coord(cast(s32) job.world_pos.x, cast(s32) job.world_pos.y, cast(s32) job.world_pos.z);
|
|
||||||
already := false;
|
|
||||||
for bake_chunk_keys { if it == ck { already = true; break; } }
|
|
||||||
if !already then array_add(*bake_chunk_keys, ck);
|
|
||||||
}
|
|
||||||
|
|
||||||
lookup_texels :: RDM_LOOKUP_SIZE * RDM_LOOKUP_SIZE;
|
// b) Populate world.rdm_lookup with normalized UV rects.
|
||||||
lookup_bytes :: lookup_texels * 4 * size_of(float);
|
if curworld.valid {
|
||||||
|
array_reset_keeping_memory(*curworld.world.rdm_lookup);
|
||||||
for chunk_key: bake_chunk_keys {
|
atlas_w := cast(float) bake.width;
|
||||||
print("Handling chunk key: %\n", chunk_key);
|
atlas_h := cast(float) bake.height;
|
||||||
bake := table_find_pointer(*rdm_chunk_bakes, chunk_key);
|
for entry: bake.entries {
|
||||||
if bake == null || bake.entries.count == 0 then continue;
|
e : Rdm_Atlas_Entry;
|
||||||
|
e.x = cast(s32) entry.world_pos.x;
|
||||||
// a) Upload per-chunk atlas to GPU.
|
e.y = cast(s32) entry.world_pos.y;
|
||||||
atlas_imgdata : sg_image_data;
|
e.z = cast(s32) entry.world_pos.z;
|
||||||
atlas_byte_size := cast(u64) bake.width * cast(u64) bake.height * 4 * size_of(float);
|
e.atlas_rect = .{
|
||||||
atlas_imgdata.subimage[0][0] = .{bake.data, atlas_byte_size};
|
cast(float) entry.x / atlas_w,
|
||||||
|
cast(float) entry.y / atlas_h,
|
||||||
atlas_desc : sg_image_desc = .{
|
cast(float) entry.w / atlas_w,
|
||||||
render_target = false,
|
cast(float) entry.h / atlas_h,
|
||||||
width = bake.width,
|
};
|
||||||
height = bake.height,
|
array_add(*curworld.world.rdm_lookup, e);
|
||||||
pixel_format = sg_pixel_format.RGBA32F,
|
|
||||||
sample_count = 1,
|
|
||||||
data = atlas_imgdata
|
|
||||||
};
|
|
||||||
atlas_image := sg_make_image(*atlas_desc);
|
|
||||||
|
|
||||||
// b) Generate lookup texture (512x512 RGBA32F).
|
|
||||||
lookup_data := cast(*float) alloc(lookup_bytes);
|
|
||||||
memset(lookup_data, 0, lookup_bytes);
|
|
||||||
|
|
||||||
atlas_w := cast(float) bake.width;
|
|
||||||
atlas_h := cast(float) bake.height;
|
|
||||||
|
|
||||||
for entry: bake.entries {
|
|
||||||
lx, ly, lz := world_to_local(cast(s32) entry.world_pos.x, cast(s32) entry.world_pos.y, cast(s32) entry.world_pos.z);
|
|
||||||
index := cast(s32) lx + cast(s32) ly * 32 + cast(s32) lz * 32 * 32 + entry.roughness * 32 * 32 * 32;
|
|
||||||
tx := index % RDM_LOOKUP_SIZE;
|
|
||||||
ty := index / RDM_LOOKUP_SIZE;
|
|
||||||
pixel_offset := (ty * RDM_LOOKUP_SIZE + tx) * 4;
|
|
||||||
lookup_data[pixel_offset + 0] = cast(float) entry.x / atlas_w;
|
|
||||||
lookup_data[pixel_offset + 1] = cast(float) entry.y / atlas_h;
|
|
||||||
lookup_data[pixel_offset + 2] = cast(float) entry.w / atlas_w;
|
|
||||||
lookup_data[pixel_offset + 3] = cast(float) entry.h / atlas_h;
|
|
||||||
}
|
|
||||||
|
|
||||||
lookup_imgdata : sg_image_data;
|
|
||||||
lookup_imgdata.subimage[0][0] = .{lookup_data, lookup_bytes};
|
|
||||||
|
|
||||||
lookup_desc : sg_image_desc = .{
|
|
||||||
render_target = false,
|
|
||||||
width = RDM_LOOKUP_SIZE,
|
|
||||||
height = RDM_LOOKUP_SIZE,
|
|
||||||
pixel_format = sg_pixel_format.RGBA32F,
|
|
||||||
sample_count = 1,
|
|
||||||
data = lookup_imgdata
|
|
||||||
};
|
|
||||||
lookup_image := sg_make_image(*lookup_desc);
|
|
||||||
free(lookup_data);
|
|
||||||
|
|
||||||
// c) Store in Chunk.
|
|
||||||
chunk := table_find_pointer(*curworld.world.chunks, chunk_key);
|
|
||||||
if chunk != null {
|
|
||||||
if chunk.rdm_valid {
|
|
||||||
sg_destroy_image(chunk.rdm_atlas);
|
|
||||||
sg_destroy_image(chunk.rdm_lookup);
|
|
||||||
}
|
}
|
||||||
chunk.rdm_atlas = atlas_image;
|
|
||||||
chunk.rdm_lookup = lookup_image;
|
|
||||||
chunk.rdm_valid = true;
|
|
||||||
chunk.rdm_dirty = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
total_entries += cast(s64) bake.entries.count;
|
// c) Save atlas + manifest to disk.
|
||||||
chunk_count += 1;
|
rdm_save_global_atlas_to_disk(bake);
|
||||||
|
if curworld.valid then rdm_save_global_manifest_to_disk(curworld.world.name, curworld.world.rdm_lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("RDM bake complete: % chunks, % total entries", chunk_count, total_entries);
|
log_info("RDM bake complete: % entries", bake.entries.count);
|
||||||
|
|
||||||
// Save baked RDM data to disk.
|
|
||||||
rdm_save_all_chunks_to_disk();
|
|
||||||
|
|
||||||
// Clean up CPU bake data.
|
|
||||||
rdm_cleanup_chunk_bakes();
|
|
||||||
|
|
||||||
|
if bake.data != null then free(bake.data);
|
||||||
|
array_free(bake.entries);
|
||||||
array_free(rdm_bake.jobs);
|
array_free(rdm_bake.jobs);
|
||||||
rdm_bake = .{};
|
rdm_bake = .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
rdm_cleanup_chunk_bakes :: () {
|
// Convert RGBA32F CPU atlas to RGBA16F and upload to g_rdm_atlas.
|
||||||
for *bake: rdm_chunk_bakes {
|
upload_global_atlas_image :: (bake: *RDM_Global_Bake) {
|
||||||
if bake.data != null then free(bake.data);
|
pixel_count := cast(s64) bake.width * cast(s64) bake.height * 4;
|
||||||
array_free(bake.entries);
|
halves := cast(*u16) alloc(pixel_count * size_of(u16));
|
||||||
}
|
defer free(halves);
|
||||||
deinit(*rdm_chunk_bakes);
|
for i: 0..pixel_count - 1 halves[i] = f32_to_f16(bake.data[i]);
|
||||||
rdm_chunk_bakes = .{};
|
|
||||||
|
imgdata : sg_image_data;
|
||||||
|
imgdata.subimage[0][0] = .{halves, cast(u64)(pixel_count * size_of(u16))};
|
||||||
|
desc : sg_image_desc = .{
|
||||||
|
render_target = false,
|
||||||
|
width = bake.width,
|
||||||
|
height = bake.height,
|
||||||
|
pixel_format = sg_pixel_format.RGBA16F,
|
||||||
|
sample_count = 1,
|
||||||
|
data = imgdata,
|
||||||
|
};
|
||||||
|
if g_rdm_atlas.id != 0 then sg_destroy_image(g_rdm_atlas);
|
||||||
|
g_rdm_atlas = sg_make_image(*desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
bake_sky_scale : float = 1.0;
|
bake_sky_scale : float = 1.0;
|
||||||
bake_sky_desaturation : float = 0.0;
|
bake_sky_desaturation : float = 1.0;
|
||||||
|
|
||||||
world_to_sky_config :: (world: World) -> Tacoma.Sky_Config {
|
world_to_sky_config :: (world: World) -> Tacoma.Sky_Config {
|
||||||
sky : Tacoma.Sky_Config;
|
sky : Tacoma.Sky_Config;
|
||||||
@ -604,14 +437,6 @@ gen_reference :: (w: s32, h: s32, eye: Vector3, target: Vector3, quality: s32, i
|
|||||||
tacoma_stop();
|
tacoma_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
gen_rdm :: (quality: s32, include_water: bool, world: World) {
|
|
||||||
tacoma_init_scene(world, include_water);
|
|
||||||
size := g_rdm_default_sizes[0];
|
|
||||||
ptr := Tacoma.tacoma_render_rdm(ctx, 0, 0, quality, size);
|
|
||||||
tacoma_handle_result(ptr, 2 * size, 3 * size);
|
|
||||||
tacoma_stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
sh_bake_start :: (quality: s32 = 50, include_water: bool = false) {
|
sh_bake_start :: (quality: s32 = 50, include_water: bool = false) {
|
||||||
if sh_bake.active then return;
|
if sh_bake.active then return;
|
||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
@ -648,9 +473,11 @@ sh_bake_tick :: () {
|
|||||||
if chunk == null then return;
|
if chunk == null then return;
|
||||||
|
|
||||||
ox, oy, oz := chunk_local_to_world(chunk_key, 0, 0, 0);
|
ox, oy, oz := chunk_local_to_world(chunk_key, 0, 0, 0);
|
||||||
|
SH_PAD :: 2.0;
|
||||||
|
SH_SPACING :: (32.0 + 2.0 * SH_PAD) / n;
|
||||||
ptr := Tacoma.tacoma_render_sh_chunk(ctx,
|
ptr := Tacoma.tacoma_render_sh_chunk(ctx,
|
||||||
cast(float) ox, cast(float) oy, cast(float) oz,
|
cast(float) ox - SH_PAD, cast(float) oy - SH_PAD, cast(float) oz - SH_PAD,
|
||||||
n, 0.5, sh_bake.quality);
|
n, SH_SPACING, sh_bake.quality);
|
||||||
|
|
||||||
tex_halfs :: tex_w * n * n * 4;
|
tex_halfs :: tex_w * n * n * 4;
|
||||||
packed : *u16 = cast(*u16) alloc(tex_halfs * size_of(u16));
|
packed : *u16 = cast(*u16) alloc(tex_halfs * size_of(u16));
|
||||||
@ -708,75 +535,49 @@ shgrid_save_to_disk :: (world_name: string, chunk_key: Chunk_Key, data: *float,
|
|||||||
builder : String_Builder;
|
builder : String_Builder;
|
||||||
header := SH_Grid_File_Header.{
|
header := SH_Grid_File_Header.{
|
||||||
magic = SH_FILE_MAGIC,
|
magic = SH_FILE_MAGIC,
|
||||||
version = 1,
|
version = 2,
|
||||||
probe_n = probe_n,
|
probe_n = probe_n,
|
||||||
};
|
};
|
||||||
write_bytes(*builder, *header, size_of(SH_Grid_File_Header));
|
write_bytes(*builder, *header, size_of(SH_Grid_File_Header));
|
||||||
total_floats := cast(s64) probe_n * probe_n * probe_n * 12;
|
total_values := cast(s64) probe_n * probe_n * probe_n * 12;
|
||||||
write_bytes(*builder, data, total_floats * size_of(float));
|
halves := NewArray(total_values, u16,, temp);
|
||||||
|
for i: 0..total_values-1 halves[i] = f32_to_f16(data[i]);
|
||||||
|
write_bytes(*builder, halves.data, total_values * size_of(u16));
|
||||||
file.write_entire_file(path, builder_to_string(*builder));
|
file.write_entire_file(path, builder_to_string(*builder));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- RDM disk persistence ---
|
rdm_save_global_atlas_to_disk :: (bake: *RDM_Global_Bake) {
|
||||||
// (RDM_File_Header, RDM_FILE_MAGIC, rdm_chunk_filename, rdm_load_from_disk
|
|
||||||
// are defined in rdm_disk.jai which is always loaded on non-WASM builds.)
|
|
||||||
|
|
||||||
rdm_save_image_to_file :: (path: string, data: *float, width: s32, height: s32) {
|
|
||||||
#if OS != .WASM {
|
#if OS != .WASM {
|
||||||
file :: #import "File";
|
file :: #import "File";
|
||||||
builder: String_Builder;
|
|
||||||
header := RDM_File_Header.{
|
|
||||||
magic = RDM_FILE_MAGIC,
|
|
||||||
width = width,
|
|
||||||
height = height,
|
|
||||||
};
|
|
||||||
write_bytes(*builder, *header, size_of(RDM_File_Header));
|
|
||||||
pixel_bytes := cast(s64) width * cast(s64) height * 4 * size_of(float);
|
|
||||||
write_bytes(*builder, data, pixel_bytes);
|
|
||||||
file.write_entire_file(path, builder_to_string(*builder));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rdm_save_all_chunks_to_disk :: () {
|
|
||||||
#if OS != .WASM {
|
|
||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
if !curworld.valid then return;
|
if !curworld.valid then return;
|
||||||
world_name := curworld.world.name;
|
path := rdm_global_atlas_filename(curworld.world.name);
|
||||||
|
|
||||||
for *bake, chunk_key: rdm_chunk_bakes {
|
builder : String_Builder;
|
||||||
print("Processing chunk....\n");
|
header := RDM_File_Header.{
|
||||||
if bake.entries.count == 0 then continue;
|
magic = RDM_FILE_MAGIC,
|
||||||
|
width = bake.width,
|
||||||
|
height = bake.height,
|
||||||
|
};
|
||||||
|
write_bytes(*builder, *header, size_of(RDM_File_Header));
|
||||||
|
// Convert RGBA32F → RGBA16F for the on-disk atlas.
|
||||||
|
pixel_count := cast(s64) bake.width * cast(s64) bake.height * 4;
|
||||||
|
halves := NewArray(pixel_count, u16,, temp);
|
||||||
|
for i: 0..pixel_count - 1 halves[i] = f32_to_f16(bake.data[i]);
|
||||||
|
write_bytes(*builder, halves.data, pixel_count * size_of(u16));
|
||||||
|
file.write_entire_file(path, builder_to_string(*builder));
|
||||||
|
log_info("Saved RDM global atlas: %", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save atlas.
|
rdm_save_global_manifest_to_disk :: (world_name: string, entries: []Rdm_Atlas_Entry) {
|
||||||
atlas_path := rdm_chunk_filename(world_name, chunk_key, "rdm_atlas");
|
#if OS != .WASM {
|
||||||
rdm_save_image_to_file(atlas_path, bake.data, bake.width, bake.height);
|
file :: #import "File";
|
||||||
|
path := rdm_global_manifest_filename(world_name);
|
||||||
// Regenerate and save lookup.
|
s := Jaison.json_write_string(entries);
|
||||||
lookup_texels :: RDM_LOOKUP_SIZE * RDM_LOOKUP_SIZE;
|
file.write_entire_file(path, s);
|
||||||
lookup_floats :: lookup_texels * 4;
|
log_info("Saved RDM manifest: % (% entries)", path, entries.count);
|
||||||
lookup_data : [lookup_floats]float;
|
|
||||||
memset(lookup_data.data, 0, size_of(type_of(lookup_data)));
|
|
||||||
|
|
||||||
atlas_w := cast(float) bake.width;
|
|
||||||
atlas_h := cast(float) bake.height;
|
|
||||||
for entry: bake.entries {
|
|
||||||
lx, ly, lz := world_to_local(cast(s32) entry.world_pos.x, cast(s32) entry.world_pos.y, cast(s32) entry.world_pos.z);
|
|
||||||
index := cast(s32) lx + cast(s32) ly * 32 + cast(s32) lz * 32 * 32 + entry.roughness * 32 * 32 * 32;
|
|
||||||
tx := index % RDM_LOOKUP_SIZE;
|
|
||||||
ty := index / RDM_LOOKUP_SIZE;
|
|
||||||
pixel_offset := (ty * RDM_LOOKUP_SIZE + tx) * 4;
|
|
||||||
lookup_data[pixel_offset + 0] = cast(float) entry.x / atlas_w;
|
|
||||||
lookup_data[pixel_offset + 1] = cast(float) entry.y / atlas_h;
|
|
||||||
lookup_data[pixel_offset + 2] = cast(float) entry.w / atlas_w;
|
|
||||||
lookup_data[pixel_offset + 3] = cast(float) entry.h / atlas_h;
|
|
||||||
}
|
|
||||||
|
|
||||||
lookup_path := rdm_chunk_filename(world_name, chunk_key, "rdm_lookup");
|
|
||||||
rdm_save_image_to_file(lookup_path, lookup_data.data, RDM_LOOKUP_SIZE, RDM_LOOKUP_SIZE);
|
|
||||||
|
|
||||||
log_info("Saved RDM data for chunk % to disk", chunk_key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ theme_ptr : GR.Overall_Theme;
|
|||||||
|
|
||||||
current_pipeline : s32 = 0;
|
current_pipeline : s32 = 0;
|
||||||
current_slot : s32 = 0;
|
current_slot : s32 = 0;
|
||||||
pipeline_names : []string = .["shadowmap", "reflection", "main", "position", "normal", "ssao", "bloom", "dof", "rdm_atlas (chunk)", "rdm_lookup (chunk)"];
|
pipeline_names : []string = .["shadowmap", "reflection", "main", "position", "normal", "ssao", "bloom", "dof", "rdm_atlas"];
|
||||||
|
|
||||||
draw_subwindow_texture_debug :: (state: *GR.Subwindow_State, r: GR.Rect, data: *void) {
|
draw_subwindow_texture_debug :: (state: *GR.Subwindow_State, r: GR.Rect, data: *void) {
|
||||||
r2 := r;
|
r2 := r;
|
||||||
@ -38,16 +38,7 @@ draw_subwindow_texture_debug :: (state: *GR.Subwindow_State, r: GR.Rect, data: *
|
|||||||
case 5; image = g_ssaobuf;
|
case 5; image = g_ssaobuf;
|
||||||
case 6; image = g_bloom_tex;
|
case 6; image = g_bloom_tex;
|
||||||
case 7; image = g_dof_tex;
|
case 7; image = g_dof_tex;
|
||||||
case 8; #through;
|
case 8; image = g_rdm_atlas;
|
||||||
case 9;
|
|
||||||
cw := get_current_world();
|
|
||||||
for chunk: cw.world.chunks {
|
|
||||||
if chunk.rdm_valid {
|
|
||||||
if current_pipeline == 6 then image = chunk.rdm_atlas;
|
|
||||||
else image = chunk.rdm_lookup;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uiTex.tex = image;
|
uiTex.tex = image;
|
||||||
|
|||||||
@ -545,20 +545,6 @@ draw_trile_editor :: () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trile_editor_shortcuts :: () {
|
trile_editor_shortcuts :: () {
|
||||||
// Tool/mode buttons are only rendered in the Toolset tab — handle shortcuts here so they work from any tab.
|
|
||||||
if is_action_start(Editor_Action.TRIXEL_TOOL_PAINT) then current_tool = .PAINT;
|
|
||||||
if is_action_start(Editor_Action.TRIXEL_TOOL_ADD) then current_tool = .ADD;
|
|
||||||
if is_action_start(Editor_Action.TRIXEL_TOOL_REMOVE) then current_tool = .REMOVE;
|
|
||||||
if is_action_start(Editor_Action.TRIXEL_MODE_POINT) then current_mode = .POINT;
|
|
||||||
if is_action_start(Editor_Action.TRIXEL_MODE_AREA) then current_mode = .AREA;
|
|
||||||
if is_action_start(Editor_Action.TRIXEL_MODE_BRUSH) then current_mode = .BRUSH;
|
|
||||||
// Save is in the Toolset tab only.
|
|
||||||
if is_action_start(Editor_Action.SAVE) {
|
|
||||||
set_trile_gfx(editor_current_trile.name, generate_trile_gfx_matias(editor_current_trile));
|
|
||||||
striles();
|
|
||||||
update_trile_thumbnail(editor_current_trile.name);
|
|
||||||
}
|
|
||||||
if is_action_start(Editor_Action.TRIXEL_VIEW_TILE) then tiling_preview_active = !tiling_preview_active;
|
|
||||||
if is_action_start(Editor_Action.TRIXEL_UNDO) then do_undo();
|
if is_action_start(Editor_Action.TRIXEL_UNDO) then do_undo();
|
||||||
if is_action_start(Editor_Action.TRIXEL_REDO) then do_redo();
|
if is_action_start(Editor_Action.TRIXEL_REDO) then do_redo();
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/main.jai
18
src/main.jai
@ -97,9 +97,9 @@ init :: () {
|
|||||||
logger = .{ func = slog_func },
|
logger = .{ func = slog_func },
|
||||||
}));
|
}));
|
||||||
sfetch_setup(*(sfetch_desc_t.{
|
sfetch_setup(*(sfetch_desc_t.{
|
||||||
max_requests = 64,
|
max_requests = 128,
|
||||||
num_channels = 2,
|
num_channels = 2,
|
||||||
num_lanes = 1,
|
num_lanes = 8,
|
||||||
logger = .{ func = slog_func },
|
logger = .{ func = slog_func },
|
||||||
}));
|
}));
|
||||||
asset_manager_init();
|
asset_manager_init();
|
||||||
@ -109,6 +109,7 @@ init :: () {
|
|||||||
logger = .{ func = slog_func },
|
logger = .{ func = slog_func },
|
||||||
stream_cb = sokol_audio_callback,
|
stream_cb = sokol_audio_callback,
|
||||||
}));
|
}));
|
||||||
|
g_audio_sample_rate = saudio_sample_rate();
|
||||||
stm_setup();
|
stm_setup();
|
||||||
state.dpi_scale = sapp_dpi_scale();
|
state.dpi_scale = sapp_dpi_scale();
|
||||||
atlas_dim := round_pow2(1024.0 * state.dpi_scale);
|
atlas_dim := round_pow2(1024.0 * state.dpi_scale);
|
||||||
@ -122,13 +123,16 @@ init :: () {
|
|||||||
|
|
||||||
state.pass_action_clear.colors[0] = .{ load_action = .CLEAR, clear_value = .{ r = 0, g = 0, b = 0, a = 0 } };
|
state.pass_action_clear.colors[0] = .{ load_action = .CLEAR, clear_value = .{ r = 0, g = 0, b = 0, a = 0 } };
|
||||||
state.pass_action_clear_gbuf.colors[0] = .{ load_action = .CLEAR, clear_value = .{ r = 0, g = 0, b = 1000, a = 0 } };
|
state.pass_action_clear_gbuf.colors[0] = .{ load_action = .CLEAR, clear_value = .{ r = 0, g = 0, b = 1000, a = 0 } };
|
||||||
|
state.pass_action_clear_gbuf.colors[2] = .{ load_action = .CLEAR, clear_value = .{ r = 0, g = 0, b = 0, a = 0 } };
|
||||||
state.pass_action.colors[0] = .{ load_action = .LOAD };
|
state.pass_action.colors[0] = .{ load_action = .LOAD };
|
||||||
|
|
||||||
|
g_mixer.paused = true;
|
||||||
|
|
||||||
useless_mem : [1]u8;
|
useless_mem : [1]u8;
|
||||||
debug_font = get_font_at_size(useless_mem, 15);
|
debug_font = get_font_at_size(useless_mem, 15);
|
||||||
load_pack("boot", true, true);
|
load_pack("boot", .BLOCK_ENGINE);
|
||||||
load_pack("core", true, false);
|
load_pack("core");
|
||||||
load_pack("game_core", true, false);
|
load_pack("game_core");
|
||||||
|
|
||||||
#if FLAG_TEST_EXE_ENGINE {
|
#if FLAG_TEST_EXE_ENGINE {
|
||||||
engine_exe_tests_add();
|
engine_exe_tests_add();
|
||||||
@ -211,6 +215,10 @@ frame :: () {
|
|||||||
init_after_core_done = true;
|
init_after_core_done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if g_mixer.paused && !in_editor_view && !show_loading_screen() {
|
||||||
|
g_mixer.paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
fonsClearState(state.fons);
|
fonsClearState(state.fons);
|
||||||
|
|
||||||
add_frame_profiling_point("After loading logic");
|
add_frame_profiling_point("After loading logic");
|
||||||
|
|||||||
@ -51,7 +51,7 @@ hot_reload_all_packs :: () {
|
|||||||
array_add(*pack_names, sprint("%", pack.name));
|
array_add(*pack_names, sprint("%", pack.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
array_reset(*g_asset_manager.main_queue);
|
array_reset(*g_asset_manager.queue);
|
||||||
|
|
||||||
g_mixer.paused = true;
|
g_mixer.paused = true;
|
||||||
g_asset_manager.hot_reload_unpause_pending = true;
|
g_asset_manager.hot_reload_unpause_pending = true;
|
||||||
@ -76,7 +76,7 @@ hot_reload_all_packs :: () {
|
|||||||
recreate_packs_on_disk();
|
recreate_packs_on_disk();
|
||||||
|
|
||||||
for name: pack_names {
|
for name: pack_names {
|
||||||
load_pack(name, true, false);
|
load_pack(name);
|
||||||
}
|
}
|
||||||
log_info("Hot-reload: queued % pack(s) for reload", pack_names.count);
|
log_info("Hot-reload: queued % pack(s) for reload", pack_names.count);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,9 +41,9 @@ Particle :: struct {
|
|||||||
definition : *Particle_Emitter_Config;
|
definition : *Particle_Emitter_Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_PARTICLES : s32 : 2048;
|
MAX_PARTICLES :: 16384;
|
||||||
|
|
||||||
g_particles : [2048] Particle;
|
g_particles : [MAX_PARTICLES] Particle;
|
||||||
g_emitter_defs : [..] Particle_Emitter_Config;
|
g_emitter_defs : [..] Particle_Emitter_Config;
|
||||||
|
|
||||||
clear_particles :: () {
|
clear_particles :: () {
|
||||||
@ -102,8 +102,11 @@ add_particle_render_tasks :: () {
|
|||||||
anim_name : string;
|
anim_name : string;
|
||||||
blend_mode : Particle_Blend_Mode;
|
blend_mode : Particle_Blend_Mode;
|
||||||
anim : *Animation;
|
anim : *Animation;
|
||||||
task : *Rendering_Task_Particles;
|
sheet : sg_image;
|
||||||
count : s32;
|
count : s32;
|
||||||
|
pos_size : *[MAX_PARTICLES]Vector4;
|
||||||
|
uv_rects : *[MAX_PARTICLES]Vector4;
|
||||||
|
colors : *[MAX_PARTICLES]Vector4;
|
||||||
}
|
}
|
||||||
|
|
||||||
batches : [..] Batch;
|
batches : [..] Batch;
|
||||||
@ -111,7 +114,6 @@ add_particle_render_tasks :: () {
|
|||||||
|
|
||||||
for *p: g_particles {
|
for *p: g_particles {
|
||||||
if !p.alive then continue;
|
if !p.alive then continue;
|
||||||
|
|
||||||
if p.definition == null then continue;
|
if p.definition == null then continue;
|
||||||
def := p.definition;
|
def := p.definition;
|
||||||
if def.animation_name.count == 0 then continue;
|
if def.animation_name.count == 0 then continue;
|
||||||
@ -127,10 +129,17 @@ add_particle_render_tasks :: () {
|
|||||||
if batch == null {
|
if batch == null {
|
||||||
anim := get_animation_from_string(def.animation_name);
|
anim := get_animation_from_string(def.animation_name);
|
||||||
if anim == null then continue;
|
if anim == null then continue;
|
||||||
array_add(*batches, .{ anim_name = def.animation_name, blend_mode = def.blend_mode, anim = anim, task = New(Rendering_Task_Particles,, temp) });
|
if anim.frames.count == 0 then continue;
|
||||||
|
array_add(*batches, .{
|
||||||
|
anim_name = def.animation_name,
|
||||||
|
blend_mode = def.blend_mode,
|
||||||
|
anim = anim,
|
||||||
|
sheet = anim.sheet,
|
||||||
|
pos_size = New([MAX_PARTICLES]Vector4,, temp),
|
||||||
|
uv_rects = New([MAX_PARTICLES]Vector4,, temp),
|
||||||
|
colors = New([MAX_PARTICLES]Vector4,, temp),
|
||||||
|
});
|
||||||
batch = *batches[batches.count - 1];
|
batch = *batches[batches.count - 1];
|
||||||
batch.task.sheet = anim.sheet;
|
|
||||||
batch.task.blend_mode = def.blend_mode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if batch.count >= MAX_PARTICLES then continue;
|
if batch.count >= MAX_PARTICLES then continue;
|
||||||
@ -140,31 +149,48 @@ add_particle_render_tasks :: () {
|
|||||||
col := lerp_color(def.color_start, def.color_end, t);
|
col := lerp_color(def.color_start, def.color_end, t);
|
||||||
|
|
||||||
idx := batch.count;
|
idx := batch.count;
|
||||||
batch.task.pos_size[idx] = .{p.position.x, p.position.y, p.position.z, size};
|
batch.pos_size.*[idx] = .{p.position.x, p.position.y, p.position.z, size};
|
||||||
|
|
||||||
anim := batch.anim;
|
anim := batch.anim;
|
||||||
frame_count := anim.frames.count;
|
frame_count := anim.frames.count;
|
||||||
if frame_count == 0 then continue;
|
|
||||||
frame := cast(s32)(t * cast(float)frame_count);
|
frame := cast(s32)(t * cast(float)frame_count);
|
||||||
if frame < 0 then frame = 0;
|
if frame < 0 then frame = 0;
|
||||||
if frame >= frame_count then frame = cast(s32)(frame_count - 1);
|
if frame >= frame_count then frame = cast(s32)(frame_count - 1);
|
||||||
f := anim.frames[frame];
|
f := anim.frames[frame];
|
||||||
batch.task.uv_rects[idx] = .{
|
batch.uv_rects.*[idx] = .{
|
||||||
cast(float)f.x / cast(float)anim.sheet_w,
|
cast(float)f.x / cast(float)anim.sheet_w,
|
||||||
cast(float)f.y / cast(float)anim.sheet_h,
|
cast(float)f.y / cast(float)anim.sheet_h,
|
||||||
cast(float)f.w / cast(float)anim.sheet_w,
|
cast(float)f.w / cast(float)anim.sheet_w,
|
||||||
cast(float)f.h / cast(float)anim.sheet_h,
|
cast(float)f.h / cast(float)anim.sheet_h,
|
||||||
};
|
};
|
||||||
|
|
||||||
batch.task.colors[idx] = col;
|
batch.colors.*[idx] = col;
|
||||||
batch.count += 1;
|
batch.count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf_task := New(Rendering_Task_Particles_Buffer,, temp);
|
||||||
|
total_count : s32 = 0;
|
||||||
|
|
||||||
for *b: batches {
|
for *b: batches {
|
||||||
if b.count > 0 {
|
if b.count <= 0 then continue;
|
||||||
b.task.count = b.count;
|
|
||||||
add_rendering_task(b.task.*);
|
instance_offset := total_count;
|
||||||
}
|
memcpy(*buf_task.pos_size[instance_offset], cast(*void)b.pos_size, b.count * size_of(Vector4));
|
||||||
|
memcpy(*buf_task.uv_rects[instance_offset], cast(*void)b.uv_rects, b.count * size_of(Vector4));
|
||||||
|
memcpy(*buf_task.colors[instance_offset], cast(*void)b.colors, b.count * size_of(Vector4));
|
||||||
|
total_count += b.count;
|
||||||
|
|
||||||
|
draw_task : Rendering_Task_Particles;
|
||||||
|
draw_task.count = b.count;
|
||||||
|
draw_task.instance_offset = instance_offset;
|
||||||
|
draw_task.blend_mode = b.blend_mode;
|
||||||
|
draw_task.sheet = b.sheet;
|
||||||
|
add_rendering_task(draw_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
if total_count > 0 {
|
||||||
|
buf_task.total_count = total_count;
|
||||||
|
add_rendering_task(buf_task.*);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
#import,dir "../../modules/sokol-jai/sokol/app"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/app"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
#import,dir "../../modules/sokol-jai/sokol/gfx"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/gfx"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
#import,dir "../../modules/sokol-jai/sokol/gl"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/gl"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
#import,dir "../../modules/sokol-jai/sokol/glue"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/glue"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
#import,dir "../../modules/sokol-jai/sokol/shape"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/shape"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
#import,dir "../../modules/sokol-jai/sokol/fontstash";
|
#import,dir "../../modules/sokol-jai/sokol/fontstash";
|
||||||
#import,dir "../../modules/sokol-jai/sokol/log"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/log"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
#import,dir "../../modules/sokol-jai/sokol/time"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/time"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
#import,dir "../../modules/sokol-jai/sokol/fetch"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/fetch"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
#import,dir "../../modules/sokol-jai/sokol/audio"(DEBUG = FLAG_RELEASE_BUILD);
|
#import,dir "../../modules/sokol-jai/sokol/audio"(DEBUG = !FLAG_RELEASE_BUILD);
|
||||||
|
|
||||||
#load "../main.jai";
|
#load "../main.jai";
|
||||||
|
|
||||||
|
|||||||
@ -18,10 +18,13 @@ Render_Command_Type :: enum {
|
|||||||
DRAW_GROUND;
|
DRAW_GROUND;
|
||||||
ADD_TRILE_POSITIONS;
|
ADD_TRILE_POSITIONS;
|
||||||
DRAW_TRILE_POSITIONS;
|
DRAW_TRILE_POSITIONS;
|
||||||
|
ADD_TRILE_RDM_POSITION;
|
||||||
|
DRAW_TRILE_RDM;
|
||||||
UPDATE_TRIXELS;
|
UPDATE_TRIXELS;
|
||||||
DRAW_TRIXELS;
|
DRAW_TRIXELS;
|
||||||
SET_LIGHT;
|
SET_LIGHT;
|
||||||
DRAW_BILLBOARD;
|
DRAW_BILLBOARD;
|
||||||
|
UPDATE_PARTICLES;
|
||||||
DRAW_PARTICLES;
|
DRAW_PARTICLES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +61,21 @@ Render_Command_Draw_Trile_Positions :: struct {
|
|||||||
offset_index : s32 = 0; // index into trile_offsets, assigned at task-conversion time
|
offset_index : s32 = 0; // index into trile_offsets, assigned at task-conversion time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Render_Command_Add_Trile_RDM_Position :: struct {
|
||||||
|
#as using c : Render_Command;
|
||||||
|
c.type = .ADD_TRILE_RDM_POSITION;
|
||||||
|
position : Vector4;
|
||||||
|
}
|
||||||
|
|
||||||
|
Render_Command_Draw_Trile_RDM :: struct {
|
||||||
|
#as using c : Render_Command;
|
||||||
|
c.type = .DRAW_TRILE_RDM;
|
||||||
|
trile : string;
|
||||||
|
conf : *World_Config;
|
||||||
|
atlas_rect : Vector4;
|
||||||
|
offset_index : s32 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Render_Command_Update_Trixels :: struct {
|
Render_Command_Update_Trixels :: struct {
|
||||||
#as using c : Render_Command;
|
#as using c : Render_Command;
|
||||||
c.type = .UPDATE_TRIXELS;
|
c.type = .UPDATE_TRIXELS;
|
||||||
@ -86,15 +104,22 @@ Render_Command_Draw_Trixels :: struct {
|
|||||||
is_secondary : bool;
|
is_secondary : bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Render_Command_Update_Particles :: struct {
|
||||||
|
#as using c : Render_Command;
|
||||||
|
c.type = .UPDATE_PARTICLES;
|
||||||
|
total_count : s32;
|
||||||
|
pos_size : [MAX_PARTICLES]Vector4;
|
||||||
|
uv_rects : [MAX_PARTICLES]Vector4;
|
||||||
|
colors : [MAX_PARTICLES]Vector4;
|
||||||
|
}
|
||||||
|
|
||||||
Render_Command_Draw_Particles :: struct {
|
Render_Command_Draw_Particles :: struct {
|
||||||
#as using c : Render_Command;
|
#as using c : Render_Command;
|
||||||
c.type = .DRAW_PARTICLES;
|
c.type = .DRAW_PARTICLES;
|
||||||
count : s32;
|
count : s32;
|
||||||
blend_mode : Particle_Blend_Mode;
|
instance_offset : s32;
|
||||||
sheet : sg_image;
|
blend_mode : Particle_Blend_Mode;
|
||||||
pos_size : [2048]Vector4;
|
sheet : sg_image;
|
||||||
uv_rects : [2048]Vector4;
|
|
||||||
colors : [2048]Vector4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Render_Command_Draw_Ground :: struct {
|
Render_Command_Draw_Ground :: struct {
|
||||||
|
|||||||
@ -8,7 +8,8 @@ bypass_postprocess : bool = false;
|
|||||||
bypass_shadows : bool = false;
|
bypass_shadows : bool = false;
|
||||||
bypass_reflections : bool = false;
|
bypass_reflections : bool = false;
|
||||||
|
|
||||||
trile_offsets : [..]s32;
|
trile_offsets : [..]s32;
|
||||||
|
trile_rdm_offsets : [..]s32;
|
||||||
|
|
||||||
current_world_config : *World_Config = null;
|
current_world_config : *World_Config = null;
|
||||||
in_shadowmap_pass : bool = false;
|
in_shadowmap_pass : bool = false;
|
||||||
@ -24,6 +25,12 @@ backend_handle_command :: (cmd: *Render_Command) {
|
|||||||
case .DRAW_TRILE_POSITIONS;
|
case .DRAW_TRILE_POSITIONS;
|
||||||
draw_command := cast(*Render_Command_Draw_Trile_Positions)cmd;
|
draw_command := cast(*Render_Command_Draw_Trile_Positions)cmd;
|
||||||
backend_draw_trile_positions(draw_command.trile, draw_command.amount, draw_command.conf, draw_command.chunk_key, draw_command.preview_mode, draw_command.offset_index);
|
backend_draw_trile_positions(draw_command.trile, draw_command.amount, draw_command.conf, draw_command.chunk_key, draw_command.preview_mode, draw_command.offset_index);
|
||||||
|
case .ADD_TRILE_RDM_POSITION;
|
||||||
|
add_command := cast(*Render_Command_Add_Trile_RDM_Position)cmd;
|
||||||
|
backend_add_trile_rdm_position(add_command.position);
|
||||||
|
case .DRAW_TRILE_RDM;
|
||||||
|
draw_command := cast(*Render_Command_Draw_Trile_RDM)cmd;
|
||||||
|
backend_draw_trile_rdm(draw_command.trile, draw_command.conf, draw_command.atlas_rect, draw_command.offset_index);
|
||||||
case .DRAW_SKY;
|
case .DRAW_SKY;
|
||||||
sky_command := cast(*Render_Command_Sky)cmd;
|
sky_command := cast(*Render_Command_Sky)cmd;
|
||||||
backend_draw_sky(sky_command.worldConfig);
|
backend_draw_sky(sky_command.worldConfig);
|
||||||
@ -53,6 +60,9 @@ backend_handle_command :: (cmd: *Render_Command) {
|
|||||||
} else {
|
} else {
|
||||||
backend_gbuffer_draw_billboard(command.position, command.animation, command.frame, command.flipX, command.faceDir);
|
backend_gbuffer_draw_billboard(command.position, command.animation, command.frame, command.flipX, command.faceDir);
|
||||||
}
|
}
|
||||||
|
case .UPDATE_PARTICLES;
|
||||||
|
update_cmd := cast(*Render_Command_Update_Particles)cmd;
|
||||||
|
backend_update_particle_buffers(update_cmd);
|
||||||
case .DRAW_PARTICLES;
|
case .DRAW_PARTICLES;
|
||||||
particles_cmd := cast(*Render_Command_Draw_Particles)cmd;
|
particles_cmd := cast(*Render_Command_Draw_Particles)cmd;
|
||||||
backend_draw_particles(particles_cmd);
|
backend_draw_particles(particles_cmd);
|
||||||
@ -215,27 +225,12 @@ backend_draw_trile_positions_main :: (trile : string, amount : s32, worldConf: *
|
|||||||
bindings.images[1] = g_ssaobuf;
|
bindings.images[1] = g_ssaobuf;
|
||||||
bindings.samplers[2] = g_shadowmap_sampler;
|
bindings.samplers[2] = g_shadowmap_sampler;
|
||||||
bindings.images[2] = g_shadowmap;
|
bindings.images[2] = g_shadowmap;
|
||||||
|
bindings.images[3] = g_brdf_lut;
|
||||||
|
bindings.images[4] = g_sh_irradiance;
|
||||||
|
bindings.samplers[3] = gPipelines.trile.bind.samplers[3];
|
||||||
|
|
||||||
// Bind RDM textures for this chunk. Fall back to a 1x1 black image when
|
|
||||||
// no baked data is available so sokol doesn't drop the draw call.
|
|
||||||
// The shader gates all RDM sampling on atlas_rect.z > 0, which the
|
|
||||||
// fallback texture returns as 0, so the ambient fallback path is taken.
|
|
||||||
curworld := get_current_world();
|
curworld := get_current_world();
|
||||||
chunk := table_find_pointer(*curworld.world.chunks, chunk_key);
|
chunk := table_find_pointer(*curworld.world.chunks, chunk_key);
|
||||||
if chunk != null && chunk.rdm_valid {
|
|
||||||
bindings.images[3] = chunk.rdm_lookup;
|
|
||||||
bindings.images[4] = chunk.rdm_atlas;
|
|
||||||
} else {
|
|
||||||
bindings.images[3] = g_rdm_fallback;
|
|
||||||
bindings.images[4] = g_rdm_fallback;
|
|
||||||
}
|
|
||||||
bindings.images[5] = g_brdf_lut;
|
|
||||||
if chunk != null && chunk.sh_valid {
|
|
||||||
bindings.images[6] = chunk.sh_probe_grid;
|
|
||||||
} else {
|
|
||||||
bindings.images[6] = g_sh_fallback;
|
|
||||||
}
|
|
||||||
bindings.samplers[3] = gPipelines.trile.bind.samplers[3];
|
|
||||||
|
|
||||||
fs_params : Trile_Fs_Params;
|
fs_params : Trile_Fs_Params;
|
||||||
fs_params.mvp_shadow = shadow_mvp.floats;
|
fs_params.mvp_shadow = shadow_mvp.floats;
|
||||||
@ -244,16 +239,14 @@ backend_draw_trile_positions_main :: (trile : string, amount : s32, worldConf: *
|
|||||||
fs_params.screen_w = w;
|
fs_params.screen_w = w;
|
||||||
fs_params.screen_h = h;
|
fs_params.screen_h = h;
|
||||||
lc := *current_lighting_config;
|
lc := *current_lighting_config;
|
||||||
fs_params.rdm_enabled = ifx in_reflection_pass then 0 else lc.rdm_enabled;
|
fs_params.ambient_intensity = lc.ambient_intensity;
|
||||||
fs_params.ambient_intensity = lc.ambient_intensity;
|
fs_params.emissive_scale = lc.emissive_scale;
|
||||||
fs_params.emissive_scale = lc.emissive_scale;
|
fs_params.indirect_diff_scale = lc.indirect_diff_scale;
|
||||||
fs_params.rdm_diff_scale = lc.rdm_diff_scale;
|
fs_params.indirect_spec_scale = lc.indirect_spec_scale;
|
||||||
fs_params.rdm_spec_scale = lc.rdm_spec_scale;
|
fs_params.ambient_color = lc.ambient_color.component;
|
||||||
fs_params.ambient_color = lc.ambient_color.component;
|
fs_params.is_preview = preview_mode;
|
||||||
fs_params.is_preview = preview_mode;
|
fs_params.indirect_tint = lc.indirect_tint.component;
|
||||||
fs_params.rdm_tint = lc.rdm_tint.component;
|
fs_params.sh_enabled = ifx (chunk != null && chunk.sh_valid && !in_reflection_pass) then cast(s32)1 else cast(s32)0;
|
||||||
fs_params.rdm_diff_saturation = worldConf.rdmDiffSaturation;
|
|
||||||
fs_params.sh_enabled = ifx (chunk != null && chunk.sh_valid && !in_reflection_pass) then cast(s32)1 else cast(s32)0;
|
|
||||||
|
|
||||||
sg_apply_bindings(*bindings);
|
sg_apply_bindings(*bindings);
|
||||||
sg_apply_uniforms(UB_trile_fs_params, *(sg_range.{ ptr = *fs_params, size = size_of(type_of(fs_params)) }));
|
sg_apply_uniforms(UB_trile_fs_params, *(sg_range.{ ptr = *fs_params, size = size_of(type_of(fs_params)) }));
|
||||||
@ -263,6 +256,73 @@ backend_draw_trile_positions_main :: (trile : string, amount : s32, worldConf: *
|
|||||||
sg_draw(0, cast(s32) trilegfx.vertex_count, amount);
|
sg_draw(0, cast(s32) trilegfx.vertex_count, amount);
|
||||||
end_frame_profiling_group("Draw trile positions");
|
end_frame_profiling_group("Draw trile positions");
|
||||||
}
|
}
|
||||||
|
backend_add_trile_rdm_position :: (position: Vector4) {
|
||||||
|
pos := position;
|
||||||
|
offset := sg_append_buffer(gPipelines.trile_rdm.bind.vertex_buffers[3], *(sg_range.{
|
||||||
|
ptr = *pos,
|
||||||
|
size = size_of(Vector4),
|
||||||
|
}));
|
||||||
|
array_add(*trile_rdm_offsets, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
backend_draw_trile_rdm :: (trile: string, worldConf: *World_Config, atlas_rect: Vector4, offset_index: s32) {
|
||||||
|
if offset_index >= trile_rdm_offsets.count then return;
|
||||||
|
|
||||||
|
trilegfx := get_trile_gfx(trile);
|
||||||
|
offset := trile_rdm_offsets[offset_index];
|
||||||
|
|
||||||
|
mvp := create_viewproj(*camera);
|
||||||
|
vs_params : Trile_Rdm_Vs_Params;
|
||||||
|
vs_params.mvp = mvp.floats;
|
||||||
|
vs_params.mvp_shadow = shadow_mvp.floats;
|
||||||
|
vs_params.camera = camera.position.component;
|
||||||
|
|
||||||
|
sg_apply_pipeline(gPipelines.trile_rdm.pipeline);
|
||||||
|
world_conf : Trile_Rdm_World_Config;
|
||||||
|
world_config_to_shader_type(worldConf, *world_conf);
|
||||||
|
world_conf.planeHeight = effective_plane_height(worldConf);
|
||||||
|
|
||||||
|
bindings : sg_bindings;
|
||||||
|
bindings.vertex_buffers[0] = trilegfx.vertex_buffer;
|
||||||
|
bindings.vertex_buffers[1] = trilegfx.normal_buffer;
|
||||||
|
bindings.vertex_buffers[2] = trilegfx.centre_buffer;
|
||||||
|
bindings.vertex_buffers[3] = gPipelines.trile_rdm.bind.vertex_buffers[3];
|
||||||
|
bindings.vertex_buffer_offsets[3] = offset;
|
||||||
|
bindings.samplers[SMP_rdm_trilesmp] = gPipelines.trile_rdm.bind.samplers[SMP_rdm_trilesmp];
|
||||||
|
bindings.samplers[SMP_rdm_shadowsmp] = g_shadowmap_sampler;
|
||||||
|
bindings.samplers[SMP_rdm_linsmp] = gPipelines.trile_rdm.bind.samplers[SMP_rdm_linsmp];
|
||||||
|
bindings.images[IMG_rdm_triletex] = trilegfx.trixel_colors;
|
||||||
|
bindings.images[IMG_rdm_ssaotex] = g_ssaobuf;
|
||||||
|
bindings.images[IMG_rdm_shadowtex] = g_shadowmap;
|
||||||
|
bindings.images[IMG_rdm_brdflut] = g_brdf_lut;
|
||||||
|
bindings.images[IMG_rdm_shirradiance] = g_sh_irradiance;
|
||||||
|
bindings.images[IMG_rdm_atlas] = g_rdm_atlas;
|
||||||
|
|
||||||
|
curworld := get_current_world();
|
||||||
|
fs_params : Trile_Rdm_Fs_Params;
|
||||||
|
fs_params.mvp_shadow = shadow_mvp.floats;
|
||||||
|
fs_params.is_reflection = 0;
|
||||||
|
w, h := get_render_size();
|
||||||
|
fs_params.screen_w = w;
|
||||||
|
fs_params.screen_h = h;
|
||||||
|
lc := *current_lighting_config;
|
||||||
|
fs_params.ambient_intensity = lc.ambient_intensity;
|
||||||
|
fs_params.emissive_scale = lc.emissive_scale;
|
||||||
|
fs_params.indirect_diff_scale = lc.indirect_diff_scale;
|
||||||
|
fs_params.indirect_spec_scale = lc.indirect_spec_scale;
|
||||||
|
fs_params.ambient_color = lc.ambient_color.component;
|
||||||
|
fs_params.is_preview = 0;
|
||||||
|
fs_params.indirect_tint = lc.indirect_tint.component;
|
||||||
|
fs_params.sh_enabled = ifx curworld.valid then cast(s32)1 else cast(s32)0;
|
||||||
|
fs_params.atlas_rect = atlas_rect.component;
|
||||||
|
|
||||||
|
sg_apply_bindings(*bindings);
|
||||||
|
sg_apply_uniforms(UB_trile_rdm_fs_params, *(sg_range.{ ptr = *fs_params, size = size_of(type_of(fs_params)) }));
|
||||||
|
sg_apply_uniforms(UB_trile_rdm_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) }));
|
||||||
|
sg_apply_uniforms(UB_trile_rdm_world_config, *(sg_range.{ ptr = *world_conf, size = size_of(type_of(world_conf)) }));
|
||||||
|
sg_draw(0, cast(s32) trilegfx.vertex_count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
backend_draw_sky :: (wc: *World_Config) {
|
backend_draw_sky :: (wc: *World_Config) {
|
||||||
mvp := create_viewproj(*camera);
|
mvp := create_viewproj(*camera);
|
||||||
vs_params : Sky_Vs_Params;
|
vs_params : Sky_Vs_Params;
|
||||||
@ -370,23 +430,27 @@ backend_gbuffer_draw_billboard :: (position: Vector3, anim: *Animation, frame_id
|
|||||||
sg_draw(0, 6, 1);
|
sg_draw(0, 6, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backend_update_particle_buffers :: (cmd: *Render_Command_Update_Particles) {
|
||||||
|
if cmd.total_count <= 0 then return;
|
||||||
|
byte_size := cast(u64)(cmd.total_count * size_of(Vector4));
|
||||||
|
inst_buf1 := gPipelines.particle_additive.bind.vertex_buffers[1];
|
||||||
|
inst_buf2 := gPipelines.particle_additive.bind.vertex_buffers[2];
|
||||||
|
inst_buf3 := gPipelines.particle_additive.bind.vertex_buffers[3];
|
||||||
|
sg_update_buffer(inst_buf1, *(sg_range.{ ptr = cmd.pos_size.data, size = byte_size }));
|
||||||
|
sg_update_buffer(inst_buf2, *(sg_range.{ ptr = cmd.uv_rects.data, size = byte_size }));
|
||||||
|
sg_update_buffer(inst_buf3, *(sg_range.{ ptr = cmd.colors.data, size = byte_size }));
|
||||||
|
}
|
||||||
|
|
||||||
backend_draw_particles :: (cmd: *Render_Command_Draw_Particles) {
|
backend_draw_particles :: (cmd: *Render_Command_Draw_Particles) {
|
||||||
if cmd.count <= 0 then return;
|
if cmd.count <= 0 then return;
|
||||||
|
|
||||||
pip := ifx cmd.blend_mode == .ALPHA then *gPipelines.particle_alpha else *gPipelines.particle_additive;
|
pip := ifx cmd.blend_mode == .ALPHA then *gPipelines.particle_alpha else *gPipelines.particle_additive;
|
||||||
|
|
||||||
sg_update_buffer(pip.bind.vertex_buffers[1], *(sg_range.{
|
byte_offset := cmd.instance_offset * size_of(Vector4);
|
||||||
ptr = cmd.pos_size.data,
|
bind := pip.bind;
|
||||||
size = cast(u64)(cmd.count * size_of(Vector4)),
|
bind.vertex_buffer_offsets[1] = byte_offset;
|
||||||
}));
|
bind.vertex_buffer_offsets[2] = byte_offset;
|
||||||
sg_update_buffer(pip.bind.vertex_buffers[2], *(sg_range.{
|
bind.vertex_buffer_offsets[3] = byte_offset;
|
||||||
ptr = cmd.uv_rects.data,
|
|
||||||
size = cast(u64)(cmd.count * size_of(Vector4)),
|
|
||||||
}));
|
|
||||||
sg_update_buffer(pip.bind.vertex_buffers[3], *(sg_range.{
|
|
||||||
ptr = cmd.colors.data,
|
|
||||||
size = cast(u64)(cmd.count * size_of(Vector4)),
|
|
||||||
}));
|
|
||||||
|
|
||||||
mvp := create_viewproj(*camera);
|
mvp := create_viewproj(*camera);
|
||||||
vs_params : Particle_Vs_Params;
|
vs_params : Particle_Vs_Params;
|
||||||
@ -394,8 +458,8 @@ backend_draw_particles :: (cmd: *Render_Command_Draw_Particles) {
|
|||||||
vs_params.cam = camera.position.component;
|
vs_params.cam = camera.position.component;
|
||||||
|
|
||||||
sg_apply_pipeline(pip.pipeline);
|
sg_apply_pipeline(pip.pipeline);
|
||||||
pip.bind.images[IMG_particle_sprite] = cmd.sheet;
|
bind.images[IMG_particle_sprite] = cmd.sheet;
|
||||||
sg_apply_bindings(*pip.bind);
|
sg_apply_bindings(*bind);
|
||||||
sg_apply_uniforms(UB_particle_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) }));
|
sg_apply_uniforms(UB_particle_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) }));
|
||||||
sg_draw(0, 6, cmd.count);
|
sg_draw(0, 6, cmd.count);
|
||||||
}
|
}
|
||||||
@ -421,6 +485,39 @@ backend_draw_ground_gbuf :: (wc: *World_Config) {
|
|||||||
sg_draw(0, 6, 1);
|
sg_draw(0, 6, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draw_sh_irradiance_pass :: () {
|
||||||
|
curworld := get_current_world();
|
||||||
|
if !curworld.valid then return;
|
||||||
|
|
||||||
|
sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, attachments = g_sh_irradiance_attach }));
|
||||||
|
sg_apply_pipeline(gPipelines.sh_irradiance.pipeline);
|
||||||
|
|
||||||
|
view := create_lookat(*camera);
|
||||||
|
inv_view := isometry_inverse(view);
|
||||||
|
|
||||||
|
lc := *current_lighting_config;
|
||||||
|
|
||||||
|
params : Sh_Deferred_Params;
|
||||||
|
params.inv_view = inv_view.floats;
|
||||||
|
params.ambient = .[lc.ambient_color.x, lc.ambient_color.y, lc.ambient_color.z, lc.ambient_intensity];
|
||||||
|
|
||||||
|
for *chunk: curworld.world.chunks {
|
||||||
|
if !chunk.sh_valid then continue;
|
||||||
|
|
||||||
|
gPipelines.sh_irradiance.bind.images[0] = g_gbuf_worldpos;
|
||||||
|
gPipelines.sh_irradiance.bind.images[1] = g_gbuf_normal;
|
||||||
|
gPipelines.sh_irradiance.bind.images[2] = chunk.sh_probe_grid;
|
||||||
|
sg_apply_bindings(*gPipelines.sh_irradiance.bind);
|
||||||
|
|
||||||
|
ck := chunk.coord;
|
||||||
|
params.chunk_origin = .[cast(float)(ck.x * 32), cast(float)(ck.y * 32), cast(float)(ck.z * 32), 0];
|
||||||
|
sg_apply_uniforms(UB_sh_deferred_params, *(sg_range.{ ptr = *params, size = size_of(type_of(params)) }));
|
||||||
|
sg_draw(0, 6, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sg_end_pass();
|
||||||
|
}
|
||||||
|
|
||||||
backend_process_command_buckets :: () {
|
backend_process_command_buckets :: () {
|
||||||
// 1. Set up textures and buffers.
|
// 1. Set up textures and buffers.
|
||||||
start_frame_profiling_group("Setup");
|
start_frame_profiling_group("Setup");
|
||||||
@ -486,6 +583,10 @@ backend_process_command_buckets :: () {
|
|||||||
sg_end_pass();
|
sg_end_pass();
|
||||||
end_frame_profiling_group("SSAO pass");
|
end_frame_profiling_group("SSAO pass");
|
||||||
|
|
||||||
|
start_frame_profiling_group("SH irradiance pass");
|
||||||
|
draw_sh_irradiance_pass();
|
||||||
|
end_frame_profiling_group("SH irradiance pass");
|
||||||
|
|
||||||
// 5. Main pass
|
// 5. Main pass
|
||||||
start_frame_profiling_group("Main pass");
|
start_frame_profiling_group("Main pass");
|
||||||
sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, attachments = g_rendertex_attachments}));
|
sg_begin_pass(*(sg_pass.{ action = state.pass_action_clear, attachments = g_rendertex_attachments}));
|
||||||
@ -554,5 +655,6 @@ backend_process_command_buckets :: () {
|
|||||||
array_reset_keeping_memory(*render_command_buckets.gbuffer);
|
array_reset_keeping_memory(*render_command_buckets.gbuffer);
|
||||||
array_reset_keeping_memory(*render_command_buckets.ui);
|
array_reset_keeping_memory(*render_command_buckets.ui);
|
||||||
array_reset_keeping_memory(*trile_offsets);
|
array_reset_keeping_memory(*trile_offsets);
|
||||||
|
array_reset_keeping_memory(*trile_rdm_offsets);
|
||||||
current_world_config = null;
|
current_world_config = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,11 @@ Gathered_Positions :: struct {
|
|||||||
positions: [..]Vector4;
|
positions: [..]Vector4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Gathered_Rdm_Position :: struct {
|
||||||
|
name: string;
|
||||||
|
position: Vector4;
|
||||||
|
}
|
||||||
|
|
||||||
extract_frustum_planes :: (mvp: Matrix4) -> [6]Vector4 {
|
extract_frustum_planes :: (mvp: Matrix4) -> [6]Vector4 {
|
||||||
planes : [6]Vector4;
|
planes : [6]Vector4;
|
||||||
m := mvp;
|
m := mvp;
|
||||||
@ -40,7 +45,7 @@ aabb_in_frustum :: (planes: [6]Vector4, bmin: Vector3, bmax: Vector3) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
create_world_rendering_tasks :: (world: *World, camera: Camera) {
|
create_world_rendering_tasks :: (world: *World, camera: Camera, plane_height: float = 0) {
|
||||||
create_sky_rendering_task(*world.conf);
|
create_sky_rendering_task(*world.conf);
|
||||||
create_set_light_rendering_task(*world.conf);
|
create_set_light_rendering_task(*world.conf);
|
||||||
|
|
||||||
@ -49,12 +54,24 @@ create_world_rendering_tasks :: (world: *World, camera: Camera) {
|
|||||||
cam_planes := extract_frustum_planes(cam_mvp);
|
cam_planes := extract_frustum_planes(cam_mvp);
|
||||||
shadow_planes := extract_frustum_planes(shadow_mvp);
|
shadow_planes := extract_frustum_planes(shadow_mvp);
|
||||||
|
|
||||||
|
reflect_cam := camera;
|
||||||
|
reflect_cam.position *= .{1, -1, 1};
|
||||||
|
reflect_cam.position.y += plane_height * 2;
|
||||||
|
reflect_cam.target *= .{1, -1, 1};
|
||||||
|
reflect_cam.target.y += plane_height * 2;
|
||||||
|
reflect_mvp := create_viewproj(*reflect_cam);
|
||||||
|
reflect_planes := extract_frustum_planes(reflect_mvp);
|
||||||
|
|
||||||
// Gather positions for camera-visible instances (all passes) and
|
// Gather positions for camera-visible instances (all passes) and
|
||||||
// shadow-only instances (chunks visible from sun but not camera).
|
// shadow-only instances (chunks visible from sun but not camera).
|
||||||
gathered : [..]Gathered_Positions;
|
gathered : [..]Gathered_Positions;
|
||||||
gathered.allocator = temp;
|
gathered.allocator = temp;
|
||||||
shad_gathered : [..]Gathered_Positions;
|
shad_gathered : [..]Gathered_Positions;
|
||||||
shad_gathered.allocator = temp;
|
shad_gathered.allocator = temp;
|
||||||
|
rdm_extra : [..]Gathered_Positions; // RDM-flagged instances visible to camera; shadow/gbuffer/reflection use base pipeline
|
||||||
|
rdm_extra.allocator = temp;
|
||||||
|
rdm_main : [..]Gathered_Rdm_Position; // one entry per RDM-flagged instance for the main pass
|
||||||
|
rdm_main.allocator = temp;
|
||||||
|
|
||||||
find_or_create :: (list: *[..]Gathered_Positions, name: string, chunk_key: Chunk_Key) -> *Gathered_Positions {
|
find_or_create :: (list: *[..]Gathered_Positions, name: string, chunk_key: Chunk_Key) -> *Gathered_Positions {
|
||||||
for *g: list.* {
|
for *g: list.* {
|
||||||
@ -70,9 +87,10 @@ create_world_rendering_tasks :: (world: *World, camera: Camera) {
|
|||||||
bmin := Vector3.{chunk.coord.x * 32.0, chunk.coord.y * 32.0, chunk.coord.z * 32.0};
|
bmin := Vector3.{chunk.coord.x * 32.0, chunk.coord.y * 32.0, chunk.coord.z * 32.0};
|
||||||
bmax := bmin + .{32, 32, 32};
|
bmax := bmin + .{32, 32, 32};
|
||||||
|
|
||||||
in_cam := aabb_in_frustum(cam_planes, bmin, bmax);
|
in_cam := aabb_in_frustum(cam_planes, bmin, bmax);
|
||||||
in_shad := aabb_in_frustum(shadow_planes, bmin, bmax);
|
in_reflect := aabb_in_frustum(reflect_planes, bmin, bmax);
|
||||||
if !in_cam && !in_shad continue;
|
in_shad := aabb_in_frustum(shadow_planes, bmin, bmax);
|
||||||
|
if !in_cam && !in_reflect && !in_shad continue;
|
||||||
|
|
||||||
for group: chunk.groups {
|
for group: chunk.groups {
|
||||||
for inst: group.instances {
|
for inst: group.instances {
|
||||||
@ -80,14 +98,24 @@ create_world_rendering_tasks :: (world: *World, camera: Camera) {
|
|||||||
imin := Vector3.{cast(float)wx, cast(float)wy, cast(float)wz};
|
imin := Vector3.{cast(float)wx, cast(float)wy, cast(float)wz};
|
||||||
imax := imin + .{1, 1, 1};
|
imax := imin + .{1, 1, 1};
|
||||||
|
|
||||||
inst_cam := in_cam && aabb_in_frustum(cam_planes, imin, imax);
|
inst_cam := in_cam && aabb_in_frustum(cam_planes, imin, imax);
|
||||||
inst_shad := in_shad && aabb_in_frustum(shadow_planes, imin, imax);
|
inst_reflect := in_reflect && aabb_in_frustum(reflect_planes, imin, imax);
|
||||||
if !inst_cam && !inst_shad continue;
|
inst_shad := in_shad && aabb_in_frustum(shadow_planes, imin, imax);
|
||||||
|
if !inst_cam && !inst_reflect && !inst_shad continue;
|
||||||
|
|
||||||
pos := Vector4.{cast(float)wx, cast(float)wy, cast(float)wz, cast(float)inst.orientation};
|
pos := Vector4.{cast(float)wx, cast(float)wy, cast(float)wz, cast(float)inst.orientation};
|
||||||
if inst_cam {
|
is_rdm := is_rdm_instance_enabled(world, wx, wy, wz);
|
||||||
target := find_or_create(*gathered, group.trile_name, chunk.coord);
|
if inst_cam || inst_reflect {
|
||||||
array_add(*target.positions, pos);
|
if is_rdm {
|
||||||
|
target := find_or_create(*rdm_extra, group.trile_name, chunk.coord);
|
||||||
|
array_add(*target.positions, pos);
|
||||||
|
if inst_cam {
|
||||||
|
array_add(*rdm_main, .{name = group.trile_name, position = pos});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target := find_or_create(*gathered, group.trile_name, chunk.coord);
|
||||||
|
array_add(*target.positions, pos);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
target := find_or_create(*shad_gathered, group.trile_name, chunk.coord);
|
target := find_or_create(*shad_gathered, group.trile_name, chunk.coord);
|
||||||
array_add(*target.positions, pos);
|
array_add(*target.positions, pos);
|
||||||
@ -115,6 +143,26 @@ create_world_rendering_tasks :: (world: *World, camera: Camera) {
|
|||||||
triletask.shadow_only = true;
|
triletask.shadow_only = true;
|
||||||
add_rendering_task(triletask);
|
add_rendering_task(triletask);
|
||||||
}
|
}
|
||||||
|
for g: rdm_extra {
|
||||||
|
if g.positions.count < 1 continue;
|
||||||
|
triletask : Rendering_Task_Trile;
|
||||||
|
triletask.trile = g.name;
|
||||||
|
triletask.chunk_key = g.chunk_key;
|
||||||
|
triletask.positions = g.positions;
|
||||||
|
triletask.worldConf = *world.conf;
|
||||||
|
triletask.skip_main = true;
|
||||||
|
add_rendering_task(triletask);
|
||||||
|
}
|
||||||
|
for r: rdm_main {
|
||||||
|
rect, found := rdm_get_atlas_rect(world, cast(s32) r.position.x, cast(s32) r.position.y, cast(s32) r.position.z);
|
||||||
|
if !found then continue;
|
||||||
|
rdmtask : Rendering_Task_Trile_RDM;
|
||||||
|
rdmtask.trile = r.name;
|
||||||
|
rdmtask.position = r.position;
|
||||||
|
rdmtask.atlas_rect = rect;
|
||||||
|
rdmtask.worldConf = *world.conf;
|
||||||
|
add_rendering_task(rdmtask);
|
||||||
|
}
|
||||||
|
|
||||||
create_ground_rendering_task(world);
|
create_ground_rendering_task(world);
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/rendering/meshgen_2.jai
Normal file
34
src/rendering/meshgen_2.jai
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
Trile_Side :: enum_flags u8 {
|
||||||
|
TOP :: 0x1; // Larger Y
|
||||||
|
LEFT :: 0x2; // Larger Z
|
||||||
|
RIGHT :: 0x4; // Smaller Z
|
||||||
|
FRONT :: 0x8; // Larger X
|
||||||
|
BACK :: 0x10; // Smaller X
|
||||||
|
BOTTOM :: 0x20; // Smaller Ys
|
||||||
|
}
|
||||||
|
|
||||||
|
trileSideValues : [6]Trile_Side = .[.TOP, .LEFT, .RIGHT, .FRONT, .BACK, .BOTTOM];
|
||||||
|
|
||||||
|
swap_xyz_for_side :: (x: int, y: int, z: int, Trile_Side) -> (int, int, int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sides_with_air_exposure :: (trileptr: *Trile, x: int, y: int, z: int) -> u8 {
|
||||||
|
res : u8 = 0;
|
||||||
|
if trileptr.trixels[x][y][z].empty then return res;
|
||||||
|
|
||||||
|
if y == 15 || trileptr.trixels[x][y+1][z].empty then res |= xx Trile_Side.TOP;
|
||||||
|
if y == 0 || trileptr.trixels[x][y-1][z].empty then res |= xx Trile_Side.BOTTOM;
|
||||||
|
if z == 15 || trileptr.trixels[x][y][z+1].empty then res |= xx Trile_Side.LEFT;
|
||||||
|
if z == 0 || trileptr.trixels[x][y][z-1].empty then res |= xx Trile_Side.RIGHT;
|
||||||
|
if x == 15 || trileptr.trixels[x+1][y][z].empty then res |= xx Trile_Side.FRONT;
|
||||||
|
if x == 0 || trileptr.trixels[x-1][y][z].empty then res |= xx Trile_Side.BACK;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
meshgen :: (trile: *Trile) {
|
||||||
|
for trileSideValues {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
SHADOWMAP_SIZE :: 1000;
|
SHADOWMAP_SIZE :: 1500;
|
||||||
|
|
||||||
Pipeline_Binding :: struct {
|
Pipeline_Binding :: struct {
|
||||||
pipeline : sg_pipeline;
|
pipeline : sg_pipeline;
|
||||||
@ -10,6 +10,7 @@ Pipeline_Binding :: struct {
|
|||||||
g_specular_lut : sg_image;
|
g_specular_lut : sg_image;
|
||||||
g_brdf_lut : sg_image;
|
g_brdf_lut : sg_image;
|
||||||
g_rdm_fallback : sg_image; // 1x1 black image used when a chunk has no baked RDM data
|
g_rdm_fallback : sg_image; // 1x1 black image used when a chunk has no baked RDM data
|
||||||
|
g_rdm_atlas : sg_image; // global RGBA16F atlas holding RDM hemispheres for flagged instances (populated in Steps 5-6)
|
||||||
g_sh_fallback : sg_image; // 1x1 black 2D image used when a chunk has no SH probe grid
|
g_sh_fallback : sg_image; // 1x1 black 2D image used when a chunk has no SH probe grid
|
||||||
|
|
||||||
g_shadowmap : sg_image;
|
g_shadowmap : sg_image;
|
||||||
@ -23,6 +24,7 @@ g_rendertex_attachments : sg_attachments;
|
|||||||
|
|
||||||
g_gbuf_position : sg_image;
|
g_gbuf_position : sg_image;
|
||||||
g_gbuf_normal : sg_image;
|
g_gbuf_normal : sg_image;
|
||||||
|
g_gbuf_worldpos : sg_image;
|
||||||
g_gbuf_depth : sg_image;
|
g_gbuf_depth : sg_image;
|
||||||
g_gbuf_attachments : sg_attachments;
|
g_gbuf_attachments : sg_attachments;
|
||||||
|
|
||||||
@ -31,6 +33,9 @@ g_ssao_noise_buf : sg_image;
|
|||||||
g_ssaobuf_depth : sg_image;
|
g_ssaobuf_depth : sg_image;
|
||||||
g_ssao_attachments : sg_attachments;
|
g_ssao_attachments : sg_attachments;
|
||||||
|
|
||||||
|
g_sh_irradiance : sg_image;
|
||||||
|
g_sh_irradiance_attach : sg_attachments;
|
||||||
|
|
||||||
g_postprocess_a : sg_image;
|
g_postprocess_a : sg_image;
|
||||||
g_postprocess_b : sg_image;
|
g_postprocess_b : sg_image;
|
||||||
g_postprocess_a_depth : sg_image;
|
g_postprocess_a_depth : sg_image;
|
||||||
@ -63,6 +68,9 @@ gPipelines : struct {
|
|||||||
// Renders sets of triles
|
// Renders sets of triles
|
||||||
trile : Pipeline_Binding;
|
trile : Pipeline_Binding;
|
||||||
|
|
||||||
|
// Per-instance variant for triles flagged with sharp-specular RDM lookup
|
||||||
|
trile_rdm : Pipeline_Binding;
|
||||||
|
|
||||||
// Depth-only shadow pass for triles (no lighting/RDM)
|
// Depth-only shadow pass for triles (no lighting/RDM)
|
||||||
trile_shadow : Pipeline_Binding;
|
trile_shadow : Pipeline_Binding;
|
||||||
|
|
||||||
@ -88,6 +96,8 @@ gPipelines : struct {
|
|||||||
|
|
||||||
ssao: Pipeline_Binding;
|
ssao: Pipeline_Binding;
|
||||||
|
|
||||||
|
sh_irradiance: Pipeline_Binding;
|
||||||
|
|
||||||
debugline : Pipeline_Binding;
|
debugline : Pipeline_Binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +112,7 @@ create_final_image :: () {
|
|||||||
img_desc := sg_image_desc.{
|
img_desc := sg_image_desc.{
|
||||||
width = w,
|
width = w,
|
||||||
height = h,
|
height = h,
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
render_target = true,
|
render_target = true,
|
||||||
};
|
};
|
||||||
depth_desc := sg_image_desc.{
|
depth_desc := sg_image_desc.{
|
||||||
@ -137,7 +147,7 @@ create_shadowmap_image :: () {
|
|||||||
img_desc := sg_image_desc.{
|
img_desc := sg_image_desc.{
|
||||||
width = w,
|
width = w,
|
||||||
height = h,
|
height = h,
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
render_target = true,
|
render_target = true,
|
||||||
};
|
};
|
||||||
g_shadowmap = sg_make_image(*depth_desc);
|
g_shadowmap = sg_make_image(*depth_desc);
|
||||||
@ -157,6 +167,7 @@ create_pipelines :: () {
|
|||||||
create_arbtri_pipeline();
|
create_arbtri_pipeline();
|
||||||
create_trixel_pipeline();
|
create_trixel_pipeline();
|
||||||
create_trile_pipeline();
|
create_trile_pipeline();
|
||||||
|
create_trile_rdm_pipeline();
|
||||||
create_trile_shadow_pipeline();
|
create_trile_shadow_pipeline();
|
||||||
create_sky_pipeline();
|
create_sky_pipeline();
|
||||||
create_plane_pipeline();
|
create_plane_pipeline();
|
||||||
@ -174,6 +185,8 @@ create_pipelines :: () {
|
|||||||
create_shadowmap_image();
|
create_shadowmap_image();
|
||||||
create_final_image();
|
create_final_image();
|
||||||
create_ssao_images();
|
create_ssao_images();
|
||||||
|
create_sh_irradiance_pipeline();
|
||||||
|
create_sh_irradiance_image();
|
||||||
create_gbuffer_impostors();
|
create_gbuffer_impostors();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +195,7 @@ create_gbuffer_images :: () {
|
|||||||
|
|
||||||
if g_gbuf_position.id != INVALID_ID then sg_destroy_image(g_gbuf_position);
|
if g_gbuf_position.id != INVALID_ID then sg_destroy_image(g_gbuf_position);
|
||||||
if g_gbuf_normal.id != INVALID_ID then sg_destroy_image(g_gbuf_normal);
|
if g_gbuf_normal.id != INVALID_ID then sg_destroy_image(g_gbuf_normal);
|
||||||
|
if g_gbuf_worldpos.id != INVALID_ID then sg_destroy_image(g_gbuf_worldpos);
|
||||||
if g_gbuf_depth.id != INVALID_ID then sg_destroy_image(g_gbuf_depth);
|
if g_gbuf_depth.id != INVALID_ID then sg_destroy_image(g_gbuf_depth);
|
||||||
|
|
||||||
img_desc := sg_image_desc.{
|
img_desc := sg_image_desc.{
|
||||||
@ -198,15 +212,17 @@ create_gbuffer_images :: () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
g_gbuf_position = sg_make_image(*img_desc);
|
g_gbuf_position = sg_make_image(*img_desc);
|
||||||
g_gbuf_normal = sg_make_image(*img_desc);
|
g_gbuf_normal = sg_make_image(*img_desc);
|
||||||
g_gbuf_depth = sg_make_image(*depth_desc);
|
g_gbuf_worldpos = sg_make_image(*img_desc);
|
||||||
|
g_gbuf_depth = sg_make_image(*depth_desc);
|
||||||
|
|
||||||
attachmentsDesc : sg_attachments_desc;
|
attachmentsDesc : sg_attachments_desc;
|
||||||
attachmentsDesc = .{
|
attachmentsDesc = .{
|
||||||
colors[0].image = g_gbuf_position,
|
colors[0].image = g_gbuf_position,
|
||||||
colors[1].image = g_gbuf_normal,
|
colors[1].image = g_gbuf_normal,
|
||||||
depth_stencil.image = g_gbuf_depth,
|
colors[2].image = g_gbuf_worldpos,
|
||||||
};
|
depth_stencil.image = g_gbuf_depth,
|
||||||
|
};
|
||||||
sg_destroy_attachments(g_gbuf_attachments);
|
sg_destroy_attachments(g_gbuf_attachments);
|
||||||
g_gbuf_attachments = sg_make_attachments(*attachmentsDesc);
|
g_gbuf_attachments = sg_make_attachments(*attachmentsDesc);
|
||||||
}
|
}
|
||||||
@ -241,7 +257,7 @@ create_trixel_pipeline :: () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
color_state := sg_color_target_state.{
|
color_state := sg_color_target_state.{
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
blend = .{
|
blend = .{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
@ -369,9 +385,10 @@ create_gbuffer_pipeline :: () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
pipeline.color_count = 2;
|
pipeline.color_count = 3;
|
||||||
pipeline.colors[0] = color_state_pos;
|
pipeline.colors[0] = color_state_pos;
|
||||||
pipeline.colors[1] = color_state_normal;
|
pipeline.colors[1] = color_state_normal;
|
||||||
|
pipeline.colors[2] = .{ pixel_format = .RGBA16F };
|
||||||
|
|
||||||
gPipelines.gbuffer.pipeline = sg_make_pipeline(*pipeline);
|
gPipelines.gbuffer.pipeline = sg_make_pipeline(*pipeline);
|
||||||
gPipelines.gbuffer.bind.samplers[0] = sg_make_sampler(*(sg_sampler_desc.{
|
gPipelines.gbuffer.bind.samplers[0] = sg_make_sampler(*(sg_sampler_desc.{
|
||||||
@ -405,7 +422,7 @@ create_trile_pipeline :: () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
color_state := sg_color_target_state.{
|
color_state := sg_color_target_state.{
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
blend = .{
|
blend = .{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
@ -433,6 +450,52 @@ create_trile_pipeline :: () {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_trile_rdm_pipeline :: () {
|
||||||
|
pipeline: sg_pipeline_desc;
|
||||||
|
shader_desc := trile_rdm_shader_desc(sg_query_backend());
|
||||||
|
pipeline.shader = sg_make_shader(*shader_desc);
|
||||||
|
pipeline.layout.buffers[0].stride = 4*3;
|
||||||
|
pipeline.layout.buffers[1].stride = 4*3;
|
||||||
|
pipeline.layout.buffers[3].step_func = .PER_INSTANCE;
|
||||||
|
|
||||||
|
instance_buffer := sg_buffer_desc.{ usage = .STREAM, size = 16 * 64 };
|
||||||
|
|
||||||
|
pipeline.layout.attrs[ATTR_trile_rdm_position] = .{ format = .FLOAT3, buffer_index = 0 };
|
||||||
|
pipeline.layout.attrs[ATTR_trile_rdm_normal] = .{ format = .FLOAT3, buffer_index = 1 };
|
||||||
|
pipeline.layout.attrs[ATTR_trile_rdm_centre] = .{ format = .FLOAT3, buffer_index = 2 };
|
||||||
|
pipeline.layout.attrs[ATTR_trile_rdm_instance] = .{ format = .FLOAT4, buffer_index = 3 };
|
||||||
|
pipeline.depth = .{
|
||||||
|
write_enabled = true,
|
||||||
|
compare = .LESS_EQUAL,
|
||||||
|
pixel_format = .DEPTH,
|
||||||
|
};
|
||||||
|
|
||||||
|
pipeline.color_count = 1;
|
||||||
|
pipeline.colors[0] = sg_color_target_state.{
|
||||||
|
pixel_format = .RGBA16F,
|
||||||
|
blend = .{
|
||||||
|
enabled = true,
|
||||||
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
|
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
gPipelines.trile_rdm.pipeline = sg_make_pipeline(*pipeline);
|
||||||
|
gPipelines.trile_rdm.bind.samplers[0] = sg_make_sampler(*(sg_sampler_desc.{
|
||||||
|
wrap_u = .CLAMP_TO_EDGE,
|
||||||
|
wrap_v = .CLAMP_TO_EDGE,
|
||||||
|
min_filter = .NEAREST,
|
||||||
|
mag_filter = .NEAREST,
|
||||||
|
}));
|
||||||
|
gPipelines.trile_rdm.bind.vertex_buffers[3] = sg_make_buffer(*instance_buffer);
|
||||||
|
gPipelines.trile_rdm.bind.samplers[3] = sg_make_sampler(*(sg_sampler_desc.{
|
||||||
|
wrap_u = .CLAMP_TO_EDGE,
|
||||||
|
wrap_v = .CLAMP_TO_EDGE,
|
||||||
|
min_filter = .LINEAR,
|
||||||
|
mag_filter = .LINEAR,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
create_trile_shadow_pipeline :: () {
|
create_trile_shadow_pipeline :: () {
|
||||||
pipeline: sg_pipeline_desc;
|
pipeline: sg_pipeline_desc;
|
||||||
shader_desc := trile_shadow_shader_desc(sg_query_backend());
|
shader_desc := trile_shadow_shader_desc(sg_query_backend());
|
||||||
@ -451,7 +514,7 @@ create_trile_shadow_pipeline :: () {
|
|||||||
pixel_format = .DEPTH,
|
pixel_format = .DEPTH,
|
||||||
};
|
};
|
||||||
pipeline.color_count = 1;
|
pipeline.color_count = 1;
|
||||||
pipeline.colors[0].pixel_format = .RGBA32F;
|
pipeline.colors[0].pixel_format = .RGBA16F;
|
||||||
gPipelines.trile_shadow.pipeline = sg_make_pipeline(*pipeline);
|
gPipelines.trile_shadow.pipeline = sg_make_pipeline(*pipeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,7 +533,7 @@ create_sky_pipeline :: () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
color_state := sg_color_target_state.{
|
color_state := sg_color_target_state.{
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
blend = .{
|
blend = .{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
@ -545,7 +608,7 @@ create_plane_pipeline_reflection_images :: () {
|
|||||||
img_desc := sg_image_desc.{
|
img_desc := sg_image_desc.{
|
||||||
width = w/3,
|
width = w/3,
|
||||||
height = h/3,
|
height = h/3,
|
||||||
pixel_format = .RGBA8,
|
pixel_format = .RGBA16F,
|
||||||
render_target = true,
|
render_target = true,
|
||||||
};
|
};
|
||||||
depth_desc := sg_image_desc.{
|
depth_desc := sg_image_desc.{
|
||||||
@ -585,7 +648,7 @@ create_plane_pipeline :: () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
color_state := sg_color_target_state.{
|
color_state := sg_color_target_state.{
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
blend = .{
|
blend = .{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
@ -785,7 +848,7 @@ create_op_pipeline :: () {
|
|||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
|
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
|
||||||
},
|
},
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
};
|
};
|
||||||
pipeline.depth = .{
|
pipeline.depth = .{
|
||||||
write_enabled = true,
|
write_enabled = true,
|
||||||
@ -845,7 +908,7 @@ create_billboard_pipeline :: () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
color_state := sg_color_target_state.{
|
color_state := sg_color_target_state.{
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
blend = .{
|
blend = .{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
@ -907,9 +970,10 @@ create_gbuffer_billboard_pipeline :: () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
pipeline.color_count = 2;
|
pipeline.color_count = 3;
|
||||||
pipeline.colors[0] = color_state_pos;
|
pipeline.colors[0] = color_state_pos;
|
||||||
pipeline.colors[1] = color_state_normal;
|
pipeline.colors[1] = color_state_normal;
|
||||||
|
pipeline.colors[2] = .{ pixel_format = .RGBA16F };
|
||||||
|
|
||||||
vertices: [4]Vector3 = .[
|
vertices: [4]Vector3 = .[
|
||||||
.{ 0.0, 0.0, 0.0},
|
.{ 0.0, 0.0, 0.0},
|
||||||
@ -923,10 +987,6 @@ create_gbuffer_billboard_pipeline :: () {
|
|||||||
0, 2, 3,
|
0, 2, 3,
|
||||||
];
|
];
|
||||||
|
|
||||||
pipeline.color_count = 2;
|
|
||||||
pipeline.colors[0] = color_state_pos;
|
|
||||||
pipeline.colors[1] = color_state_normal;
|
|
||||||
|
|
||||||
gPipelines.gbuffer_billboard.pipeline = sg_make_pipeline(*pipeline);
|
gPipelines.gbuffer_billboard.pipeline = sg_make_pipeline(*pipeline);
|
||||||
|
|
||||||
ibuffer := sg_buffer_desc.{ type = .INDEXBUFFER, data = .{ ptr = indices.data, size = 6 * 2 } };
|
ibuffer := sg_buffer_desc.{ type = .INDEXBUFFER, data = .{ ptr = indices.data, size = 6 * 2 } };
|
||||||
@ -960,7 +1020,7 @@ create_mix_pipeline :: () {
|
|||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
|
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
|
||||||
},
|
},
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
};
|
};
|
||||||
pipeline.depth = .{
|
pipeline.depth = .{
|
||||||
write_enabled = true,
|
write_enabled = true,
|
||||||
@ -1014,9 +1074,10 @@ create_bloom_pipeline :: () {
|
|||||||
pipeline.layout.attrs[ATTR_bloom_position] = .{ format = .FLOAT2 };
|
pipeline.layout.attrs[ATTR_bloom_position] = .{ format = .FLOAT2 };
|
||||||
pipeline.layout.attrs[ATTR_bloom_uv] = .{ format = .FLOAT2 };
|
pipeline.layout.attrs[ATTR_bloom_uv] = .{ format = .FLOAT2 };
|
||||||
pipeline.index_type = .UINT16;
|
pipeline.index_type = .UINT16;
|
||||||
|
pipeline.depth.pixel_format = .NONE;
|
||||||
|
|
||||||
pipeline.color_count = 1;
|
pipeline.color_count = 1;
|
||||||
pipeline.colors[0] = .{ pixel_format = .RGBA32F };
|
pipeline.colors[0] = .{ pixel_format = .RGBA16F };
|
||||||
|
|
||||||
gPipelines.bloom.pipeline = sg_make_pipeline(*pipeline);
|
gPipelines.bloom.pipeline = sg_make_pipeline(*pipeline);
|
||||||
|
|
||||||
@ -1060,9 +1121,10 @@ create_dof_pipeline :: () {
|
|||||||
pipeline.layout.attrs[ATTR_dof_position] = .{ format = .FLOAT2 };
|
pipeline.layout.attrs[ATTR_dof_position] = .{ format = .FLOAT2 };
|
||||||
pipeline.layout.attrs[ATTR_dof_uv] = .{ format = .FLOAT2 };
|
pipeline.layout.attrs[ATTR_dof_uv] = .{ format = .FLOAT2 };
|
||||||
pipeline.index_type = .UINT16;
|
pipeline.index_type = .UINT16;
|
||||||
|
pipeline.depth.pixel_format = .NONE;
|
||||||
|
|
||||||
pipeline.color_count = 1;
|
pipeline.color_count = 1;
|
||||||
pipeline.colors[0] = .{ pixel_format = .RGBA32F };
|
pipeline.colors[0] = .{ pixel_format = .RGBA16F };
|
||||||
|
|
||||||
gPipelines.dof.pipeline = sg_make_pipeline(*pipeline);
|
gPipelines.dof.pipeline = sg_make_pipeline(*pipeline);
|
||||||
|
|
||||||
@ -1097,6 +1159,53 @@ create_dof_pipeline :: () {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_sh_irradiance_image :: () {
|
||||||
|
w, h := get_render_size();
|
||||||
|
if g_sh_irradiance.id != INVALID_ID then sg_destroy_image(g_sh_irradiance);
|
||||||
|
g_sh_irradiance = sg_make_image(*(sg_image_desc.{
|
||||||
|
width = w / 2,
|
||||||
|
height = h / 2,
|
||||||
|
pixel_format = .RGBA16F,
|
||||||
|
render_target = true,
|
||||||
|
sample_count = 1,
|
||||||
|
}));
|
||||||
|
sg_destroy_attachments(g_sh_irradiance_attach);
|
||||||
|
g_sh_irradiance_attach = sg_make_attachments(*(sg_attachments_desc.{
|
||||||
|
colors[0].image = g_sh_irradiance,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
create_sh_irradiance_pipeline :: () {
|
||||||
|
pipeline: sg_pipeline_desc;
|
||||||
|
shader_desc := sh_deferred_shader_desc(sg_query_backend());
|
||||||
|
pipeline.shader = sg_make_shader(*shader_desc);
|
||||||
|
pipeline.layout.attrs[ATTR_sh_deferred_position] = .{ format = .FLOAT2 };
|
||||||
|
pipeline.layout.attrs[ATTR_sh_deferred_uv] = .{ format = .FLOAT2 };
|
||||||
|
pipeline.index_type = .UINT16;
|
||||||
|
pipeline.depth.pixel_format = .NONE;
|
||||||
|
pipeline.color_count = 1;
|
||||||
|
pipeline.colors[0] = .{ pixel_format = .RGBA16F };
|
||||||
|
gPipelines.sh_irradiance.pipeline = sg_make_pipeline(*pipeline);
|
||||||
|
|
||||||
|
quad_vertices : [16]float = .[
|
||||||
|
-1.0, 1.0, 0.0, flip_if_plat(1.0),
|
||||||
|
-1.0, -1.0, 0.0, flip_if_plat(0.0),
|
||||||
|
1.0, -1.0, 1.0, flip_if_plat(0.0),
|
||||||
|
1.0, 1.0, 1.0, flip_if_plat(1.0),
|
||||||
|
];
|
||||||
|
quad_indices : [6]u16 = .[ 0, 1, 2, 0, 2, 3 ];
|
||||||
|
vbuffer := sg_buffer_desc.{ size = size_of(float) * 16, data = .{ ptr = quad_vertices.data, size = 16 * 4 }};
|
||||||
|
ibuffer := sg_buffer_desc.{ size = size_of(u16) * 6, data = .{ ptr = quad_indices.data, size = 6 * 2 }, type = .INDEXBUFFER };
|
||||||
|
gPipelines.sh_irradiance.bind.vertex_buffers[0] = sg_make_buffer(*vbuffer);
|
||||||
|
gPipelines.sh_irradiance.bind.index_buffer = sg_make_buffer(*ibuffer);
|
||||||
|
gPipelines.sh_irradiance.bind.samplers[0] = sg_make_sampler(*(sg_sampler_desc.{
|
||||||
|
wrap_u = .CLAMP_TO_EDGE,
|
||||||
|
wrap_v = .CLAMP_TO_EDGE,
|
||||||
|
min_filter = .NEAREST,
|
||||||
|
mag_filter = .NEAREST,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
create_ssao_images :: () {
|
create_ssao_images :: () {
|
||||||
if g_ssaobuf.id != INVALID_ID then sg_destroy_image(g_ssaobuf);
|
if g_ssaobuf.id != INVALID_ID then sg_destroy_image(g_ssaobuf);
|
||||||
if g_ssaobuf_depth.id != INVALID_ID then sg_destroy_image(g_ssaobuf_depth);
|
if g_ssaobuf_depth.id != INVALID_ID then sg_destroy_image(g_ssaobuf_depth);
|
||||||
@ -1109,7 +1218,7 @@ create_ssao_images :: () {
|
|||||||
width = w/2,
|
width = w/2,
|
||||||
height = h/2,
|
height = h/2,
|
||||||
render_target = true,
|
render_target = true,
|
||||||
pixel_format = .RGBA8
|
pixel_format = .RGBA16F
|
||||||
};
|
};
|
||||||
img_desc.sample_count = 1;
|
img_desc.sample_count = 1;
|
||||||
g_ssaobuf = sg_make_image(*img_desc);
|
g_ssaobuf = sg_make_image(*img_desc);
|
||||||
@ -1152,7 +1261,7 @@ create_ssao_images :: () {
|
|||||||
bloom_img_desc := sg_image_desc.{
|
bloom_img_desc := sg_image_desc.{
|
||||||
width = w/8,
|
width = w/8,
|
||||||
height = h/8,
|
height = h/8,
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
render_target = true,
|
render_target = true,
|
||||||
sample_count = 1,
|
sample_count = 1,
|
||||||
};
|
};
|
||||||
@ -1167,7 +1276,7 @@ create_ssao_images :: () {
|
|||||||
dof_img_desc := sg_image_desc.{
|
dof_img_desc := sg_image_desc.{
|
||||||
width = cast(s32)((cast(float)w)/1.5),
|
width = cast(s32)((cast(float)w)/1.5),
|
||||||
height = cast(s32)((cast(float)h)/1.5),
|
height = cast(s32)((cast(float)h)/1.5),
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
render_target = true,
|
render_target = true,
|
||||||
sample_count = 1,
|
sample_count = 1,
|
||||||
};
|
};
|
||||||
@ -1196,7 +1305,7 @@ create_ssao_pipeline :: () {
|
|||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
|
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
|
||||||
},
|
},
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
};
|
};
|
||||||
|
|
||||||
pipeline.color_count = 1;
|
pipeline.color_count = 1;
|
||||||
@ -1273,9 +1382,6 @@ init_brdf_lut :: () {
|
|||||||
g_brdf_lut = sg_make_image(*desc);
|
g_brdf_lut = sg_make_image(*desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1x1 black image for RDM slots when no baked data is present.
|
|
||||||
// The lookup texture returning all zeros makes atlas_rect.z == 0,
|
|
||||||
// so the shader's fallback ambient path is taken.
|
|
||||||
{
|
{
|
||||||
pixels : [4]u8 = .[0, 0, 0, 0];
|
pixels : [4]u8 = .[0, 0, 0, 0];
|
||||||
imgdata : sg_image_data;
|
imgdata : sg_image_data;
|
||||||
@ -1288,7 +1394,6 @@ init_brdf_lut :: () {
|
|||||||
};
|
};
|
||||||
g_rdm_fallback = sg_make_image(*desc);
|
g_rdm_fallback = sg_make_image(*desc);
|
||||||
|
|
||||||
// 1x1 RGBA16F 2D texture — fallback when a chunk has no SH probe grid.
|
|
||||||
zero_sh : [4]u16 = .[0, 0, 0, 0];
|
zero_sh : [4]u16 = .[0, 0, 0, 0];
|
||||||
sh_imgdata : sg_image_data;
|
sh_imgdata : sg_image_data;
|
||||||
sh_imgdata.subimage[0][0] = .{ zero_sh.data, size_of(type_of(zero_sh)) };
|
sh_imgdata.subimage[0][0] = .{ zero_sh.data, size_of(type_of(zero_sh)) };
|
||||||
@ -1299,6 +1404,17 @@ init_brdf_lut :: () {
|
|||||||
data = sh_imgdata,
|
data = sh_imgdata,
|
||||||
};
|
};
|
||||||
g_sh_fallback = sg_make_image(*sh_desc);
|
g_sh_fallback = sg_make_image(*sh_desc);
|
||||||
|
|
||||||
|
zero_atlas : [4]u16 = .[0, 0, 0, 0];
|
||||||
|
atlas_imgdata : sg_image_data;
|
||||||
|
atlas_imgdata.subimage[0][0] = .{ zero_atlas.data, size_of(type_of(zero_atlas)) };
|
||||||
|
atlas_desc := sg_image_desc.{
|
||||||
|
width = 1,
|
||||||
|
height = 1,
|
||||||
|
pixel_format = .RGBA16F,
|
||||||
|
data = atlas_imgdata,
|
||||||
|
};
|
||||||
|
g_rdm_atlas = sg_make_image(*atlas_desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1383,7 +1499,7 @@ create_particle_pipeline :: () {
|
|||||||
pipeline.color_count = 1;
|
pipeline.color_count = 1;
|
||||||
|
|
||||||
pipeline.colors[0] = .{
|
pipeline.colors[0] = .{
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
blend = .{
|
blend = .{
|
||||||
enabled = true,
|
enabled = true,
|
||||||
src_factor_rgb = .SRC_ALPHA,
|
src_factor_rgb = .SRC_ALPHA,
|
||||||
@ -1460,7 +1576,7 @@ create_debugline_pipeline :: () {
|
|||||||
pixel_format = .DEPTH,
|
pixel_format = .DEPTH,
|
||||||
};
|
};
|
||||||
color_state := sg_color_target_state.{
|
color_state := sg_color_target_state.{
|
||||||
pixel_format = .RGBA32F,
|
pixel_format = .RGBA16F,
|
||||||
};
|
};
|
||||||
pipeline.color_count = 1;
|
pipeline.color_count = 1;
|
||||||
pipeline.colors[0] = color_state;
|
pipeline.colors[0] = color_state;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
Post_Process :: struct {
|
Post_Process :: struct {
|
||||||
exposure : float = 1.0; @Slider,0,3,0.1;
|
exposure : float = 1.0; @Slider,0,3,0.1;
|
||||||
contrast : float = 1.0; @Slider,0,6,0.1;
|
contrast : float = 1.0; @Slider,0,3,0.1;
|
||||||
saturation : float = 1.0; @Slider,0.0,2.0,0.1;
|
saturation : float = 1.0; @Slider,0.0,5.0,0.1;
|
||||||
gamma : float = 1.0; @Slider,0.3,3.0,0.1;
|
gamma : float = 1.0; @Slider,0.3,3.0,0.1;
|
||||||
tonemap : float = 0.0; @Slider,0,1,1;
|
tonemap : float = 0.0; @Slider,0,1,1;
|
||||||
ssao : float = 0.0; @Slider,0,5,0.1;
|
ssao : float = 0.0; @Slider,0,5,0.1;
|
||||||
@ -32,13 +32,12 @@ Post_Process :: struct {
|
|||||||
current_post_process : Post_Process;
|
current_post_process : Post_Process;
|
||||||
|
|
||||||
Lighting_Config :: struct {
|
Lighting_Config :: struct {
|
||||||
rdm_enabled : s32 = 1; @Slider,0,1,1
|
ambient_intensity : float = 0.35; @Slider,0,2,0.05
|
||||||
ambient_intensity : float = 0.35; @Slider,0,2,0.05
|
emissive_scale : float = 5.0; @Slider,0,20,0.5
|
||||||
emissive_scale : float = 5.0; @Slider,0,20,0.5
|
indirect_diff_scale : float = 1.0; @Slider,0,3,0.1
|
||||||
rdm_diff_scale : float = 1.0; @Slider,0,3,0.1
|
indirect_spec_scale : float = 1.0; @Slider,0,3,0.1
|
||||||
rdm_spec_scale : float = 1.0; @Slider,0,3,0.1
|
ambient_color : Vector3 = .{0.3,0.3,0.4}; @Color
|
||||||
ambient_color : Vector3 = .{0.3,0.3,0.4}; @Color
|
indirect_tint : Vector3 = .{1.05,1.0,0.9}; @Color
|
||||||
rdm_tint : Vector3 = .{1.05,1.0,0.9}; @Color
|
|
||||||
}
|
}
|
||||||
|
|
||||||
current_lighting_config : Lighting_Config;
|
current_lighting_config : Lighting_Config;
|
||||||
|
|||||||
@ -32,6 +32,7 @@ on_window_resize :: () {
|
|||||||
create_plane_pipeline_reflection_images();
|
create_plane_pipeline_reflection_images();
|
||||||
create_final_image();
|
create_final_image();
|
||||||
create_ssao_images();
|
create_ssao_images();
|
||||||
|
create_sh_irradiance_image();
|
||||||
// Reset glyph cache so stale sizes from resizing don't fill the atlas.
|
// Reset glyph cache so stale sizes from resizing don't fill the atlas.
|
||||||
w, h : s32;
|
w, h : s32;
|
||||||
fonsGetAtlasSize(state.fons, *w, *h);
|
fonsGetAtlasSize(state.fons, *w, *h);
|
||||||
|
|||||||
@ -58,7 +58,7 @@ create_shadow_viewproj :: (cam: *Camera, conf: *World_Config) -> Matrix4 {
|
|||||||
B.z, C.z, A.z, 0,
|
B.z, C.z, A.z, 0,
|
||||||
-dot(B, sunCameraPosition), -dot(C, sunCameraPosition), -dot(A, sunCameraPosition), 1
|
-dot(B, sunCameraPosition), -dot(C, sunCameraPosition), -dot(A, sunCameraPosition), 1
|
||||||
};
|
};
|
||||||
proj := matrix_ortho(-60, 60, -60, 60, 0, 100);
|
proj := matrix_ortho(-30, 30, -30, 30, 0, 100);
|
||||||
return view*proj;
|
return view*proj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,11 @@ Rendering_Task_Type :: enum {
|
|||||||
SET_CAMERA;
|
SET_CAMERA;
|
||||||
SET_LIGHT;
|
SET_LIGHT;
|
||||||
TRILE; // We need to add an ability to invalidate buffer instead of updating it constantly. Also probably have a buffer for static world triles and one for moving ones.
|
TRILE; // We need to add an ability to invalidate buffer instead of updating it constantly. Also probably have a buffer for static world triles and one for moving ones.
|
||||||
|
TRILE_RDM;
|
||||||
TRIXELS;
|
TRIXELS;
|
||||||
BILLBOARD;
|
BILLBOARD;
|
||||||
PARTICLES;
|
PARTICLES;
|
||||||
|
PARTICLES_BUFFER;
|
||||||
};
|
};
|
||||||
|
|
||||||
Rendering_Task :: struct {
|
Rendering_Task :: struct {
|
||||||
@ -59,6 +61,16 @@ Rendering_Task_Trile :: struct {
|
|||||||
worldConf : *World_Config;
|
worldConf : *World_Config;
|
||||||
preview_mode : s32 = 0; // 0=normal, 1=add preview (blue), 2=delete preview (red)
|
preview_mode : s32 = 0; // 0=normal, 1=add preview (blue), 2=delete preview (red)
|
||||||
shadow_only : bool = false; // only submit to shadow bucket (frustum-culled from camera)
|
shadow_only : bool = false; // only submit to shadow bucket (frustum-culled from camera)
|
||||||
|
skip_main : bool = false; // RDM-flagged instances still cast shadows / write gbuffer / reflect, but main pass uses the RDM pipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
Rendering_Task_Trile_RDM :: struct {
|
||||||
|
#as using t : Rendering_Task;
|
||||||
|
t.type = .TRILE_RDM;
|
||||||
|
trile : string;
|
||||||
|
position : Vector4; // xyz=world position, w=orientation
|
||||||
|
atlas_rect : Vector4; // global atlas UV rect (zeros until Step 6 wires the manifest)
|
||||||
|
worldConf : *World_Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
Rendering_Task_Trixels :: struct {
|
Rendering_Task_Trixels :: struct {
|
||||||
@ -73,14 +85,21 @@ Rendering_Task_Trixels :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rendering_Task_Particles :: struct {
|
Rendering_Task_Particles :: struct {
|
||||||
|
#as using t : Rendering_Task;
|
||||||
|
t.type = .PARTICLES;
|
||||||
|
count : s32;
|
||||||
|
instance_offset : s32;
|
||||||
|
blend_mode : Particle_Blend_Mode;
|
||||||
|
sheet : sg_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rendering_Task_Particles_Buffer :: struct {
|
||||||
#as using t : Rendering_Task;
|
#as using t : Rendering_Task;
|
||||||
t.type = .PARTICLES;
|
t.type = .PARTICLES_BUFFER;
|
||||||
count : s32;
|
total_count : s32;
|
||||||
blend_mode : Particle_Blend_Mode;
|
pos_size : [MAX_PARTICLES]Vector4;
|
||||||
sheet : sg_image;
|
uv_rects : [MAX_PARTICLES]Vector4;
|
||||||
pos_size : [2048]Vector4;
|
colors : [MAX_PARTICLES]Vector4;
|
||||||
uv_rects : [2048]Vector4;
|
|
||||||
colors : [2048]Vector4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rendering_Task_Set_Camera :: struct {
|
Rendering_Task_Set_Camera :: struct {
|
||||||
@ -103,6 +122,7 @@ rendering_tasklist : [..]*Rendering_Task;
|
|||||||
|
|
||||||
tasks_to_commands :: () {
|
tasks_to_commands :: () {
|
||||||
trile_add_counter: s32 = 0;
|
trile_add_counter: s32 = 0;
|
||||||
|
trile_rdm_add_counter: s32 = 0;
|
||||||
for rendering_tasklist {
|
for rendering_tasklist {
|
||||||
if it.type == {
|
if it.type == {
|
||||||
case .SET_LIGHT;
|
case .SET_LIGHT;
|
||||||
@ -144,12 +164,28 @@ tasks_to_commands :: () {
|
|||||||
array_add(*render_command_buckets.shadow, drawPositionsCmd);
|
array_add(*render_command_buckets.shadow, drawPositionsCmd);
|
||||||
} else if trileTask.preview_mode != 0 {
|
} else if trileTask.preview_mode != 0 {
|
||||||
array_add(*render_command_buckets.main, drawPositionsCmd);
|
array_add(*render_command_buckets.main, drawPositionsCmd);
|
||||||
|
} else if trileTask.skip_main {
|
||||||
|
array_add(*render_command_buckets.reflection, drawPositionsCmd);
|
||||||
|
array_add(*render_command_buckets.gbuffer, drawPositionsCmd);
|
||||||
|
array_add(*render_command_buckets.shadow, drawPositionsCmd);
|
||||||
} else {
|
} else {
|
||||||
array_add(*render_command_buckets.reflection, drawPositionsCmd);
|
array_add(*render_command_buckets.reflection, drawPositionsCmd);
|
||||||
array_add(*render_command_buckets.main, drawPositionsCmd);
|
array_add(*render_command_buckets.main, drawPositionsCmd);
|
||||||
array_add(*render_command_buckets.gbuffer, drawPositionsCmd);
|
array_add(*render_command_buckets.gbuffer, drawPositionsCmd);
|
||||||
array_add(*render_command_buckets.shadow, drawPositionsCmd);
|
array_add(*render_command_buckets.shadow, drawPositionsCmd);
|
||||||
}
|
}
|
||||||
|
case .TRILE_RDM;
|
||||||
|
rdmTask := (cast(*Rendering_Task_Trile_RDM)it);
|
||||||
|
addCmd := New(Render_Command_Add_Trile_RDM_Position,, temp);
|
||||||
|
addCmd.position = rdmTask.position;
|
||||||
|
array_add(*render_command_buckets.setup, addCmd);
|
||||||
|
drawCmd := New(Render_Command_Draw_Trile_RDM,, temp);
|
||||||
|
drawCmd.trile = rdmTask.trile;
|
||||||
|
drawCmd.conf = rdmTask.worldConf;
|
||||||
|
drawCmd.atlas_rect = rdmTask.atlas_rect;
|
||||||
|
drawCmd.offset_index = trile_rdm_add_counter;
|
||||||
|
trile_rdm_add_counter += 1;
|
||||||
|
array_add(*render_command_buckets.main, drawCmd);
|
||||||
case .SKY;
|
case .SKY;
|
||||||
command := New(Render_Command_Sky,, temp);
|
command := New(Render_Command_Sky,, temp);
|
||||||
command.worldConfig = (cast(*Rendering_Task_Sky)it).worldConfig;
|
command.worldConfig = (cast(*Rendering_Task_Sky)it).worldConfig;
|
||||||
@ -173,15 +209,21 @@ tasks_to_commands :: () {
|
|||||||
array_add(*render_command_buckets.main, commandDrawBillboard);
|
array_add(*render_command_buckets.main, commandDrawBillboard);
|
||||||
array_add(*render_command_buckets.shadow, commandDrawBillboard);
|
array_add(*render_command_buckets.shadow, commandDrawBillboard);
|
||||||
array_add(*render_command_buckets.reflection, commandDrawBillboard);
|
array_add(*render_command_buckets.reflection, commandDrawBillboard);
|
||||||
|
case .PARTICLES_BUFFER;
|
||||||
|
bufTask := cast(*Rendering_Task_Particles_Buffer)it;
|
||||||
|
uploadCmd := New(Render_Command_Update_Particles,, temp);
|
||||||
|
uploadCmd.total_count = bufTask.total_count;
|
||||||
|
memcpy(uploadCmd.pos_size.data, bufTask.pos_size.data, bufTask.total_count * size_of(Vector4));
|
||||||
|
memcpy(uploadCmd.uv_rects.data, bufTask.uv_rects.data, bufTask.total_count * size_of(Vector4));
|
||||||
|
memcpy(uploadCmd.colors.data, bufTask.colors.data, bufTask.total_count * size_of(Vector4));
|
||||||
|
array_add(*render_command_buckets.setup, uploadCmd);
|
||||||
case .PARTICLES;
|
case .PARTICLES;
|
||||||
particleTask := (cast(*Rendering_Task_Particles)it);
|
particleTask := cast(*Rendering_Task_Particles)it;
|
||||||
drawCmd := New(Render_Command_Draw_Particles,, temp);
|
drawCmd := New(Render_Command_Draw_Particles,, temp);
|
||||||
drawCmd.count = particleTask.count;
|
drawCmd.count = particleTask.count;
|
||||||
drawCmd.blend_mode = particleTask.blend_mode;
|
drawCmd.instance_offset = particleTask.instance_offset;
|
||||||
drawCmd.sheet = particleTask.sheet;
|
drawCmd.blend_mode = particleTask.blend_mode;
|
||||||
memcpy(drawCmd.pos_size.data, particleTask.pos_size.data, particleTask.count * size_of(Vector4));
|
drawCmd.sheet = particleTask.sheet;
|
||||||
memcpy(drawCmd.uv_rects.data, particleTask.uv_rects.data, particleTask.count * size_of(Vector4));
|
|
||||||
memcpy(drawCmd.colors.data, particleTask.colors.data, particleTask.count * size_of(Vector4));
|
|
||||||
array_add(*render_command_buckets.main, drawCmd);
|
array_add(*render_command_buckets.main, drawCmd);
|
||||||
array_add(*render_command_buckets.reflection, drawCmd);
|
array_add(*render_command_buckets.reflection, drawCmd);
|
||||||
case .SET_CAMERA;
|
case .SET_CAMERA;
|
||||||
|
|||||||
@ -45,10 +45,18 @@ Blob :: struct {
|
|||||||
g_settings : Settings_State;
|
g_settings : Settings_State;
|
||||||
g_settings_config : Settings_Menu_Config;
|
g_settings_config : Settings_Menu_Config;
|
||||||
|
|
||||||
MAIN_ITEMS :: string.["Resume", "Settings", "Exit"];
|
#if OS == .WASM {
|
||||||
|
MAIN_ITEMS :: string.["Resume", "Settings"];
|
||||||
|
} else {
|
||||||
|
MAIN_ITEMS :: string.["Resume", "Settings", "Exit"];
|
||||||
|
}
|
||||||
SETTINGS_ITEMS :: string.["Audio", "Graphics"];
|
SETTINGS_ITEMS :: string.["Audio", "Graphics"];
|
||||||
AUDIO_LABELS :: string.["Master Volume", "Music Volume", "Sound Effects"];
|
AUDIO_LABELS :: string.["Master Volume", "Music Volume", "Sound Effects"];
|
||||||
GRAPHICS_ITEMS :: string.["Fullscreen"];
|
#if OS == .WASM {
|
||||||
|
GRAPHICS_ITEMS :: string.[];
|
||||||
|
} else {
|
||||||
|
GRAPHICS_ITEMS :: string.["Fullscreen"];
|
||||||
|
}
|
||||||
|
|
||||||
page_items :: () -> []string {
|
page_items :: () -> []string {
|
||||||
if g_settings.page == .MAIN return MAIN_ITEMS;
|
if g_settings.page == .MAIN return MAIN_ITEMS;
|
||||||
@ -223,7 +231,7 @@ handle_enter :: (index: s32 = -1) {
|
|||||||
if i == 0 then navigate_to(.AUDIO);
|
if i == 0 then navigate_to(.AUDIO);
|
||||||
if i == 1 then navigate_to(.GRAPHICS);
|
if i == 1 then navigate_to(.GRAPHICS);
|
||||||
case .GRAPHICS;
|
case .GRAPHICS;
|
||||||
if i == 0 then sapp_toggle_fullscreen();
|
#if OS != .WASM { if i == 0 then sapp_toggle_fullscreen(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,8 +430,10 @@ draw_settings_menu :: () {
|
|||||||
bt.label_theme.alignment = GR.Text_Alignment.Center;
|
bt.label_theme.alignment = GR.Text_Alignment.Center;
|
||||||
|
|
||||||
label := items[i];
|
label := items[i];
|
||||||
if g_settings.page == .GRAPHICS && i == 0
|
#if OS != .WASM {
|
||||||
label = tprint("Fullscreen: %", ifx sapp_is_fullscreen() then "On" else "Off");
|
if g_settings.page == .GRAPHICS && i == 0
|
||||||
|
label = tprint("Fullscreen: %", ifx sapp_is_fullscreen() then "On" else "Off");
|
||||||
|
}
|
||||||
|
|
||||||
pressed, _, _ := GR.button(r, label, *bt, identifier = cast(s64)i);
|
pressed, _, _ := GR.button(r, label, *bt, identifier = cast(s64)i);
|
||||||
if pressed then handle_enter(cast(s32)i);
|
if pressed then handle_enter(cast(s32)i);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1648
src/shaders/jai/shader_sh_deferred.jai
Normal file
1648
src/shaders/jai/shader_sh_deferred.jai
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5593
src/shaders/jai/shader_trile_rdm.jai
Normal file
5593
src/shaders/jai/shader_trile_rdm.jai
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@ in vec4 instance;
|
|||||||
|
|
||||||
out vec3 view_space_pos;
|
out vec3 view_space_pos;
|
||||||
out vec3 view_space_normal;
|
out vec3 view_space_normal;
|
||||||
|
out vec3 v_world_pos;
|
||||||
|
|
||||||
mat3 gbuf_rot_x(float a) { float c=cos(a),s=sin(a); return mat3(1,0,0, 0,c,-s, 0,s,c); }
|
mat3 gbuf_rot_x(float a) { float c=cos(a),s=sin(a); return mat3(1,0,0, 0,c,-s, 0,s,c); }
|
||||||
mat3 gbuf_rot_z(float a) { float c=cos(a),s=sin(a); return mat3(c,-s,0, s,c,0, 0,0,1); }
|
mat3 gbuf_rot_z(float a) { float c=cos(a),s=sin(a); return mat3(c,-s,0, s,c,0, 0,0,1); }
|
||||||
@ -39,6 +40,7 @@ void main() {
|
|||||||
gl_Position = mvp * world_pos;
|
gl_Position = mvp * world_pos;
|
||||||
view_space_pos = view_pos_4.xyz;
|
view_space_pos = view_pos_4.xyz;
|
||||||
view_space_normal = mat3(view_matrix) * normal.xyz;
|
view_space_normal = mat3(view_matrix) * normal.xyz;
|
||||||
|
v_world_pos = world_pos.xyz;
|
||||||
} else {
|
} else {
|
||||||
int ori = int(round(instance.w));
|
int ori = int(round(instance.w));
|
||||||
mat3 rot = gbuf_get_orientation_matrix(ori);
|
mat3 rot = gbuf_get_orientation_matrix(ori);
|
||||||
@ -49,6 +51,7 @@ void main() {
|
|||||||
gl_Position = mvp * world_pos;
|
gl_Position = mvp * world_pos;
|
||||||
view_space_pos = view_pos_4.xyz;
|
view_space_pos = view_pos_4.xyz;
|
||||||
view_space_normal = mat3(view_matrix) * (rot * normal.xyz);
|
view_space_normal = mat3(view_matrix) * (rot * normal.xyz);
|
||||||
|
v_world_pos = world_pos.xyz;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
@ -57,13 +60,16 @@ void main() {
|
|||||||
|
|
||||||
in vec3 view_space_pos;
|
in vec3 view_space_pos;
|
||||||
in vec3 view_space_normal;
|
in vec3 view_space_normal;
|
||||||
|
in vec3 v_world_pos;
|
||||||
|
|
||||||
layout(location=0) out vec4 out_position;
|
layout(location=0) out vec4 out_position;
|
||||||
layout(location=1) out vec4 out_normal;
|
layout(location=1) out vec4 out_normal;
|
||||||
|
layout(location=2) out vec4 out_worldpos;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
out_position = vec4(view_space_pos, 1.0);
|
out_position = vec4(view_space_pos, 1.0);
|
||||||
out_normal = vec4(normalize(view_space_normal), 1.0);
|
out_normal = vec4(normalize(view_space_normal), 1.0);
|
||||||
|
out_worldpos = vec4(v_world_pos, 1.0);
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
102
src/shaders/shader_sh_deferred.glsl
Normal file
102
src/shaders/shader_sh_deferred.glsl
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
@vs vs_sh_deferred
|
||||||
|
in vec2 position;
|
||||||
|
in vec2 uv;
|
||||||
|
out vec2 quad_uv;
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
quad_uv = uv;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@fs fs_sh_deferred
|
||||||
|
|
||||||
|
layout(binding=0) uniform texture2D gbuf_worldpos;
|
||||||
|
layout(binding=1) uniform texture2D gbuf_norm;
|
||||||
|
layout(binding=2) uniform texture2D sh_chunk;
|
||||||
|
layout(binding=0) uniform sampler sh_smp;
|
||||||
|
|
||||||
|
layout(binding=0) uniform sh_deferred_params {
|
||||||
|
mat4 inv_view;
|
||||||
|
vec4 chunk_origin;
|
||||||
|
vec4 ambient; // rgb = ambient color, a = ambient intensity
|
||||||
|
};
|
||||||
|
|
||||||
|
in vec2 quad_uv;
|
||||||
|
out vec4 frag_color;
|
||||||
|
|
||||||
|
const float PI = 3.14159265359;
|
||||||
|
|
||||||
|
vec3 sh_eval(ivec3 probe, vec3 N) {
|
||||||
|
int base = probe.x * 3;
|
||||||
|
int row = probe.z * 64 + probe.y;
|
||||||
|
vec4 t0 = texelFetch(sampler2D(sh_chunk, sh_smp), ivec2(base, row), 0);
|
||||||
|
vec4 t1 = texelFetch(sampler2D(sh_chunk, sh_smp), ivec2(base+1, row), 0);
|
||||||
|
vec4 t2 = texelFetch(sampler2D(sh_chunk, sh_smp), ivec2(base+2, row), 0);
|
||||||
|
float x = N.x, y = N.y, z = N.z;
|
||||||
|
float r = 0.886227*t0.x + 1.023327*(t0.w*x + t0.y*y + t0.z*z);
|
||||||
|
float g = 0.886227*t1.x + 1.023327*(t1.w*x + t1.y*y + t1.z*z);
|
||||||
|
float b = 0.886227*t2.x + 1.023327*(t2.w*x + t2.y*y + t2.z*z);
|
||||||
|
return max(vec3(r, g, b) / PI, vec3(0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
float sh_probe_energy(ivec3 probe) {
|
||||||
|
int base = probe.x * 3;
|
||||||
|
int row = probe.z * 64 + probe.y;
|
||||||
|
vec4 t0 = texelFetch(sampler2D(sh_chunk, sh_smp), ivec2(base, row), 0);
|
||||||
|
vec4 t1 = texelFetch(sampler2D(sh_chunk, sh_smp), ivec2(base+1, row), 0);
|
||||||
|
vec4 t2 = texelFetch(sampler2D(sh_chunk, sh_smp), ivec2(base+2, row), 0);
|
||||||
|
return max(0.886227 * (t0.x + t1.x + t2.x), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 sh_eval_trilinear(ivec3 p0, ivec3 p1, vec3 t, vec3 N) {
|
||||||
|
float wx[2] = float[2](1.0 - t.x, t.x);
|
||||||
|
float wy[2] = float[2](1.0 - t.y, t.y);
|
||||||
|
float wz[2] = float[2](1.0 - t.z, t.z);
|
||||||
|
vec3 result = vec3(0.0);
|
||||||
|
vec3 unweighted = vec3(0.0);
|
||||||
|
float total_w = 0.0;
|
||||||
|
for (int iz = 0; iz < 2; iz++) {
|
||||||
|
for (int iy = 0; iy < 2; iy++) {
|
||||||
|
for (int ix = 0; ix < 2; ix++) {
|
||||||
|
ivec3 probe = ivec3(
|
||||||
|
ix == 0 ? p0.x : p1.x,
|
||||||
|
iy == 0 ? p0.y : p1.y,
|
||||||
|
iz == 0 ? p0.z : p1.z
|
||||||
|
);
|
||||||
|
vec3 sh = sh_eval(probe, N);
|
||||||
|
float triw = wx[ix] * wy[iy] * wz[iz];
|
||||||
|
float w = triw * sh_probe_energy(probe);
|
||||||
|
result += sh * w;
|
||||||
|
unweighted += sh * triw;
|
||||||
|
total_w += w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec3 amb = ambient.rgb * ambient.a;
|
||||||
|
return total_w > 0.001 ? result / total_w : max(unweighted, amb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 wp_sample = texture(sampler2D(gbuf_worldpos, sh_smp), quad_uv);
|
||||||
|
if (wp_sample.a < 0.5) discard;
|
||||||
|
|
||||||
|
vec3 world_pos = wp_sample.xyz;
|
||||||
|
|
||||||
|
vec3 cmin = chunk_origin.xyz;
|
||||||
|
vec3 cmax = cmin + vec3(32.0);
|
||||||
|
if (any(lessThan(world_pos, cmin)) || any(greaterThanEqual(world_pos, cmax))) discard;
|
||||||
|
|
||||||
|
vec3 view_norm = normalize(texture(sampler2D(gbuf_norm, sh_smp), quad_uv).xyz);
|
||||||
|
vec3 world_norm = normalize(mat3(inv_view) * view_norm);
|
||||||
|
|
||||||
|
const float SH_PAD = 2.0;
|
||||||
|
const float SH_SPACING = (32.0 + 2.0 * SH_PAD) / 64.0;
|
||||||
|
vec3 probe_f = clamp((world_pos - (cmin - vec3(SH_PAD))) / SH_SPACING, vec3(0.0), vec3(63.0));
|
||||||
|
ivec3 p0 = ivec3(floor(probe_f));
|
||||||
|
ivec3 p1 = min(p0 + ivec3(1), ivec3(63));
|
||||||
|
|
||||||
|
frag_color = vec4(sh_eval_trilinear(p0, p1, fract(probe_f), world_norm), 1.0);
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@program sh_deferred vs_sh_deferred fs_sh_deferred
|
||||||
@ -17,7 +17,6 @@ out vec3 vpos;
|
|||||||
out vec3 ipos;
|
out vec3 ipos;
|
||||||
out vec4 fnormal;
|
out vec4 fnormal;
|
||||||
out vec3 orig_normal;
|
out vec3 orig_normal;
|
||||||
out vec3 trileCenter;
|
|
||||||
out vec3 cv;
|
out vec3 cv;
|
||||||
|
|
||||||
mat3 rot_x(float a) { float c=cos(a),s=sin(a); return mat3(1,0,0, 0,c,-s, 0,s,c); }
|
mat3 rot_x(float a) { float c=cos(a),s=sin(a); return mat3(1,0,0, 0,c,-s, 0,s,c); }
|
||||||
@ -52,7 +51,6 @@ void main() {
|
|||||||
ipos = position.xyz;
|
ipos = position.xyz;
|
||||||
cam = camera;
|
cam = camera;
|
||||||
cv = normalize(camera - vpos);
|
cv = normalize(camera - vpos);
|
||||||
trileCenter = instance.xyz + vec3(0.5);
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@ -86,7 +84,6 @@ in vec3 vpos;
|
|||||||
in vec3 ipos;
|
in vec3 ipos;
|
||||||
in vec4 fnormal;
|
in vec4 fnormal;
|
||||||
in vec3 orig_normal;
|
in vec3 orig_normal;
|
||||||
in vec3 trileCenter;
|
|
||||||
in vec3 cv;
|
in vec3 cv;
|
||||||
out vec4 frag_color;
|
out vec4 frag_color;
|
||||||
|
|
||||||
@ -95,36 +92,27 @@ layout(binding=3) uniform trile_fs_params {
|
|||||||
int is_reflection;
|
int is_reflection;
|
||||||
int screen_h;
|
int screen_h;
|
||||||
int screen_w;
|
int screen_w;
|
||||||
int rdm_enabled;
|
|
||||||
float ambient_intensity;
|
float ambient_intensity;
|
||||||
float emissive_scale;
|
float emissive_scale;
|
||||||
float rdm_diff_scale;
|
float indirect_diff_scale;
|
||||||
float rdm_spec_scale;
|
float indirect_spec_scale;
|
||||||
vec3 ambient_color;
|
vec3 ambient_color;
|
||||||
int is_preview;
|
int is_preview;
|
||||||
vec3 rdm_tint;
|
vec3 indirect_tint;
|
||||||
float rdm_diff_saturation;
|
|
||||||
int sh_enabled;
|
int sh_enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(binding = 0) uniform texture2D triletex;
|
layout(binding = 0) uniform texture2D triletex;
|
||||||
layout(binding = 0) uniform sampler trilesmp;
|
layout(binding = 0) uniform sampler trilesmp;
|
||||||
layout(binding = 1) uniform texture2D ssaotex;
|
layout(binding = 1) uniform texture2D ssaotex;
|
||||||
layout(binding = 1) uniform sampler ssaosmp;
|
|
||||||
layout(binding = 2) uniform texture2D shadowtex;
|
layout(binding = 2) uniform texture2D shadowtex;
|
||||||
layout(binding = 2) uniform sampler shadowsmp;
|
layout(binding = 2) uniform sampler shadowsmp;
|
||||||
layout(binding = 3) uniform texture2D rdm_lookup;
|
layout(binding = 3) uniform texture2D brdf_lut;
|
||||||
layout(binding = 4) uniform texture2D rdm_atlas;
|
layout(binding = 4) uniform texture2D sh_irradiance;
|
||||||
layout(binding = 5) uniform texture2D brdf_lut;
|
layout(binding = 3) uniform sampler linsmp;
|
||||||
layout(binding = 6) uniform texture2D sh_chunk;
|
|
||||||
layout(binding = 3) uniform sampler rdmsmp;
|
|
||||||
|
|
||||||
const float PI = 3.1415927;
|
const float PI = 3.1415927;
|
||||||
|
const float ROUGHNESS_SPEC_CUTOFF = 0.7;
|
||||||
const float ROUGHNESS_RAYMARCH_MAX = 0.2; // Below this roughness, actually try to get sharp reflection from RDM.
|
|
||||||
const float ROUGHNESS_SPEC_CUTOFF = 0.7; // Above this roughness, disregard RDM specular lighting entirely.
|
|
||||||
|
|
||||||
// ---- SKY ----
|
|
||||||
|
|
||||||
const float cirrus = 0.5;
|
const float cirrus = 0.5;
|
||||||
|
|
||||||
@ -164,8 +152,6 @@ vec3 sky_reflect(vec3 R, vec3 sunpos) {
|
|||||||
return sky(R, sunpos);
|
return sky(R, sunpos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- PBR ----
|
|
||||||
|
|
||||||
float DistributionGGX(vec3 N, vec3 H, float roughness) {
|
float DistributionGGX(vec3 N, vec3 H, float roughness) {
|
||||||
float a = roughness * roughness;
|
float a = roughness * roughness;
|
||||||
float a2 = a * a;
|
float a2 = a * a;
|
||||||
@ -192,222 +178,9 @@ vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
|
|||||||
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- RDM HELPERS ----
|
|
||||||
|
|
||||||
// Hemioct encoding (Cigolle2014)
|
|
||||||
vec2 rdm_hemioct(vec3 v, int face) {
|
|
||||||
vec3 vc = v;
|
|
||||||
if (face / 2 == 0) { vc.z = v.y; vc.y = v.z; }
|
|
||||||
if (face / 2 == 2) { vc.z = v.x; vc.x = v.z; }
|
|
||||||
if (face % 2 == 1) { vc.z *= -1.0; }
|
|
||||||
|
|
||||||
vec2 p = vc.xy * (1.0 / (abs(vc.x) + abs(vc.y) + vc.z));
|
|
||||||
return vec2(p.x + p.y, p.x - p.y) * 0.5 + 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rdm_face_from_normal(vec3 N) {
|
|
||||||
vec3 a = abs(N);
|
|
||||||
if (a.y >= a.x && a.y >= a.z) return N.y >= 0.0 ? 0 : 1;
|
|
||||||
if (a.z >= a.x && a.z >= a.y) return N.z >= 0.0 ? 2 : 3;
|
|
||||||
return N.x >= 0.0 ? 4 : 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 rdm_atlas_rect(ivec3 local_pos, int roughness) {
|
|
||||||
int rdm_index = local_pos.x + local_pos.y * 32 + local_pos.z * 1024 + roughness * 32768;
|
|
||||||
return texelFetch(sampler2D(rdm_lookup, trilesmp), ivec2(rdm_index % 512, rdm_index / 512), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ivec2 rdm_face_offset(vec4 rect, int face, int rdmSize, ivec2 atlasSize) {
|
|
||||||
int col = face % 2;
|
|
||||||
int row = face / 2;
|
|
||||||
return ivec2(int(rect.x * float(atlasSize.x)) + col * rdmSize,
|
|
||||||
int(rect.y * float(atlasSize.y)) + row * rdmSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 rdm_face_uv(int face) {
|
|
||||||
if (face <= 1) return vec2(ipos.x, ipos.z);
|
|
||||||
if (face <= 3) return vec2(ipos.x, ipos.y);
|
|
||||||
return vec2(ipos.z, ipos.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- RDM SPECULAR ----
|
|
||||||
|
|
||||||
vec3 rdm_spec_raymarch(vec3 N, vec3 V, vec3 diff, int face, ivec2 faceOffset, int rdmSize, vec2 atlasInvSize) {
|
|
||||||
vec3 reflected = reflect(V, N);
|
|
||||||
float maxDist = 20.0;
|
|
||||||
int steps = 40;
|
|
||||||
float stepSize = maxDist / float(steps);
|
|
||||||
|
|
||||||
for (int i = 0; i < steps; i++) {
|
|
||||||
float t = stepSize * float(i + 1);
|
|
||||||
vec3 samplePos = diff + t * reflected;
|
|
||||||
if (dot(samplePos, N) < 0.0) continue;
|
|
||||||
|
|
||||||
vec3 dir = normalize(samplePos);
|
|
||||||
vec2 hemiUV = rdm_hemioct(dir, face);
|
|
||||||
vec2 texCoord = (vec2(faceOffset) + hemiUV * float(rdmSize)) * atlasInvSize;
|
|
||||||
vec4 s = texture(sampler2D(rdm_atlas, rdmsmp), texCoord, 0);
|
|
||||||
|
|
||||||
float dist = length(samplePos);
|
|
||||||
if (s.a > 0.0 && s.a < dist && s.a + stepSize > dist)
|
|
||||||
return s.rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sky_reflect(reflected, sunPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 rdm_spec_single(vec3 N, vec3 V, vec3 diff, int face, ivec2 faceOffset, int rdmSize, vec2 atlasInvSize) {
|
|
||||||
vec3 reflected = reflect(V, N);
|
|
||||||
vec3 sampleDir = normalize(diff + 2.0 * reflected);
|
|
||||||
vec2 hemiUV = rdm_hemioct(sampleDir, face);
|
|
||||||
vec2 texCoord = (vec2(faceOffset) + hemiUV * float(rdmSize)) * atlasInvSize;
|
|
||||||
return texture(sampler2D(rdm_atlas, rdmsmp), texCoord).rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 rdm_sample_diff_probe(vec3 N, ivec3 local_pos, vec3 fallback) {
|
|
||||||
vec4 rect = rdm_atlas_rect(local_pos, 7);
|
|
||||||
if (rect.z <= 0.0) return fallback;
|
|
||||||
|
|
||||||
int face = rdm_face_from_normal(N);
|
|
||||||
int rdmSize = int(pow(2.0, float((7 - 7) + 1)));
|
|
||||||
ivec2 atlasSize = textureSize(sampler2D(rdm_atlas, rdmsmp), 0);
|
|
||||||
ivec2 fOff = rdm_face_offset(rect, face, rdmSize, atlasSize);
|
|
||||||
vec2 pos = rdm_hemioct(N, face);
|
|
||||||
return texelFetch(sampler2D(rdm_atlas, rdmsmp),
|
|
||||||
ivec2(fOff.x + int(pos.x * float(rdmSize)),
|
|
||||||
fOff.y + int(pos.y * float(rdmSize))), 0).rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
int isign(float f) { return f < 0.0 ? -1 : 1; }
|
|
||||||
|
|
||||||
vec3 smix(vec3 a, vec3 b, float t) {
|
|
||||||
float power = 1.6;
|
|
||||||
float st = pow(t, power) / (pow(t, power) + pow(1.0 - t, power));
|
|
||||||
return mix(a, b, st);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 rdm_indirect_diffuse(vec3 N, vec3 diff, ivec3 local_pos) {
|
|
||||||
int face = rdm_face_from_normal(N);
|
|
||||||
vec3 ambient = vec3(0.3, 0.3, 0.4);
|
|
||||||
|
|
||||||
vec2 delta;
|
|
||||||
if (face <= 1) delta = vec2(diff.x, diff.z);
|
|
||||||
else if (face <= 3) delta = vec2(diff.x, diff.y);
|
|
||||||
else delta = vec2(diff.z, diff.y);
|
|
||||||
|
|
||||||
ivec3 s1, s2, s3;
|
|
||||||
if (face <= 1) {
|
|
||||||
s1 = ivec3(isign(delta.x), 0, 0);
|
|
||||||
s2 = ivec3(0, 0, isign(delta.y));
|
|
||||||
s3 = ivec3(isign(delta.x), 0, isign(delta.y));
|
|
||||||
} else if (face <= 3) {
|
|
||||||
s1 = ivec3(isign(delta.x), 0, 0);
|
|
||||||
s2 = ivec3(0, isign(delta.y), 0);
|
|
||||||
s3 = ivec3(isign(delta.x), isign(delta.y), 0);
|
|
||||||
} else {
|
|
||||||
s1 = ivec3(0, 0, isign(delta.x));
|
|
||||||
s2 = ivec3(0, isign(delta.y), 0);
|
|
||||||
s3 = ivec3(0, isign(delta.y), isign(delta.x));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 p0 = rdm_sample_diff_probe(N, clamp(local_pos, ivec3(0), ivec3(31)), ambient);
|
|
||||||
vec3 p1 = rdm_sample_diff_probe(N, clamp(local_pos + s1, ivec3(0), ivec3(31)), ambient);
|
|
||||||
vec3 p2 = rdm_sample_diff_probe(N, clamp(local_pos + s2, ivec3(0), ivec3(31)), ambient);
|
|
||||||
vec3 p3 = rdm_sample_diff_probe(N, clamp(local_pos + s1 + s2,ivec3(0), ivec3(31)), ambient);
|
|
||||||
|
|
||||||
return smix(smix(p0, p1, abs(delta.x)),
|
|
||||||
smix(p2, p3, abs(delta.x)),
|
|
||||||
abs(delta.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- SH PROBE GRID ----
|
|
||||||
// Each probe stores 27 L2 SH coefficients (9 per RGB channel), packed into
|
|
||||||
// 3 RGBA16F texels per probe along the X axis of a 192x4096 2D texture.
|
|
||||||
// Row = probe.z * 64 + probe.y, col = probe.x * 3 + k.
|
|
||||||
// Texel layout per probe (px,py,pz):
|
|
||||||
// t0: R.c0-3 t1: G.c0-3 t2: B.c0-3
|
|
||||||
// Probe index from chunk-local world position p (0..32 range):
|
|
||||||
// ivec3(floor(p * 2.0)) clamped to [0,63]
|
|
||||||
// SH evaluation: Lambertian irradiance (L1 only), A0=PI, A1=2PI/3.
|
|
||||||
|
|
||||||
vec3 sh_eval(ivec3 probe, vec3 N) {
|
|
||||||
int base = probe.x * 3;
|
|
||||||
int row = probe.z * 64 + probe.y;
|
|
||||||
vec4 t0 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base, row), 0);
|
|
||||||
vec4 t1 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base+1, row), 0);
|
|
||||||
vec4 t2 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base+2, row), 0);
|
|
||||||
|
|
||||||
float x = N.x, y = N.y, z = N.z;
|
|
||||||
float r = 0.886227*t0.x + 1.023327*(t0.w*x + t0.y*y + t0.z*z);
|
|
||||||
float g = 0.886227*t1.x + 1.023327*(t1.w*x + t1.y*y + t1.z*z);
|
|
||||||
float b = 0.886227*t2.x + 1.023327*(t2.w*x + t2.y*y + t2.z*z);
|
|
||||||
|
|
||||||
return max(vec3(r, g, b) / PI, vec3(0.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum of L0 irradiance across RGB — proxy for total incoming energy.
|
|
||||||
// Near-zero means the probe is buried inside solid geometry.
|
|
||||||
float sh_probe_energy(ivec3 probe) {
|
|
||||||
int base = probe.x * 3;
|
|
||||||
int row = probe.z * 64 + probe.y;
|
|
||||||
vec4 t0 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base, row), 0);
|
|
||||||
vec4 t1 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base+1, row), 0);
|
|
||||||
vec4 t2 = texelFetch(sampler2D(sh_chunk, rdmsmp), ivec2(base+2, row), 0);
|
|
||||||
return max(0.886227 * (t0.x + t1.x + t2.x), 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trilinear SH evaluation with confidence weighting.
|
|
||||||
// Probes with near-zero energy (buried in geometry) are downweighted
|
|
||||||
// so they don't pull the result toward black.
|
|
||||||
vec3 sh_eval_trilinear(ivec3 p0, ivec3 p1, vec3 t, vec3 N) {
|
|
||||||
float wx[2] = float[2](1.0 - t.x, t.x);
|
|
||||||
float wy[2] = float[2](1.0 - t.y, t.y);
|
|
||||||
float wz[2] = float[2](1.0 - t.z, t.z);
|
|
||||||
|
|
||||||
vec3 result = vec3(0.0);
|
|
||||||
float total_w = 0.0;
|
|
||||||
|
|
||||||
for (int iz = 0; iz < 2; iz++) {
|
|
||||||
for (int iy = 0; iy < 2; iy++) {
|
|
||||||
for (int ix = 0; ix < 2; ix++) {
|
|
||||||
ivec3 probe = ivec3(
|
|
||||||
ix == 0 ? p0.x : p1.x,
|
|
||||||
iy == 0 ? p0.y : p1.y,
|
|
||||||
iz == 0 ? p0.z : p1.z
|
|
||||||
);
|
|
||||||
float w = wx[ix] * wy[iy] * wz[iz] * sh_probe_energy(probe);
|
|
||||||
result += sh_eval(probe, N) * w;
|
|
||||||
total_w += w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return total_w > 0.001 ? result / total_w : vec3(0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- HSV ----
|
|
||||||
|
|
||||||
vec3 rgb2hsv(vec3 c) {
|
|
||||||
vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
|
|
||||||
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
|
||||||
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
|
||||||
float d = q.x - min(q.w, q.y);
|
|
||||||
float e = 1.0e-10;
|
|
||||||
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 hsv2rgb(vec3 c) {
|
|
||||||
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
|
|
||||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
|
||||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- MAIN ----
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
if (vpos.y < planeHeight - 0.01 && is_reflection == 1) discard;
|
if (vpos.y < planeHeight - 0.01 && is_reflection == 1) discard;
|
||||||
|
|
||||||
// Get trixel material.
|
|
||||||
vec3 sample_pos = ipos - orig_normal * 0.02;
|
vec3 sample_pos = ipos - orig_normal * 0.02;
|
||||||
vec4 trixel_material;
|
vec4 trixel_material;
|
||||||
int maxSteps = is_reflection == 1 ? 1 : 3;
|
int maxSteps = is_reflection == 1 ? 1 : 3;
|
||||||
@ -438,14 +211,12 @@ void main() {
|
|||||||
metallic = float((packed >> 3) & 0x3) / 3.0;
|
metallic = float((packed >> 3) & 0x3) / 3.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid noise in normals which appears for some reason.
|
|
||||||
vec3 absN = abs(fnormal.xyz);
|
vec3 absN = abs(fnormal.xyz);
|
||||||
vec3 N;
|
vec3 N;
|
||||||
if (absN.x >= absN.y && absN.x >= absN.z) N = vec3(sign(fnormal.x), 0.0, 0.0);
|
if (absN.x >= absN.y && absN.x >= absN.z) N = vec3(sign(fnormal.x), 0.0, 0.0);
|
||||||
else if (absN.y >= absN.x && absN.y >= absN.z) N = vec3(0.0, sign(fnormal.y), 0.0);
|
else if (absN.y >= absN.x && absN.y >= absN.z) N = vec3(0.0, sign(fnormal.y), 0.0);
|
||||||
else N = vec3(0.0, 0.0, sign(fnormal.z));
|
else N = vec3(0.0, 0.0, sign(fnormal.z));
|
||||||
|
|
||||||
// Simplified lighting evaluation for planar reflection.
|
|
||||||
if (is_reflection == 1) {
|
if (is_reflection == 1) {
|
||||||
vec3 L = normalize(sunPosition);
|
vec3 L = normalize(sunPosition);
|
||||||
float NdotL = max(dot(N, L), 0.0);
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
@ -453,7 +224,6 @@ void main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 1. VIEW / LIGHT VECTORS ----
|
|
||||||
vec3 V = normalize(cam - vpos);
|
vec3 V = normalize(cam - vpos);
|
||||||
vec3 L = normalize(sunPosition);
|
vec3 L = normalize(sunPosition);
|
||||||
vec3 H = normalize(V + L);
|
vec3 H = normalize(V + L);
|
||||||
@ -461,14 +231,12 @@ void main() {
|
|||||||
float NdotV = max(dot(N, V), 0.0);
|
float NdotV = max(dot(N, V), 0.0);
|
||||||
float HdotV = max(dot(H, V), 0.0);
|
float HdotV = max(dot(H, V), 0.0);
|
||||||
|
|
||||||
// ---- 2. PBR TERMS ----
|
|
||||||
vec3 F0 = mix(vec3(0.04), albedo, metallic);
|
vec3 F0 = mix(vec3(0.04), albedo, metallic);
|
||||||
vec3 F = fresnelSchlick(HdotV, F0);
|
vec3 F = fresnelSchlick(HdotV, F0);
|
||||||
float NDF = DistributionGGX(N, H, roughness);
|
float NDF = DistributionGGX(N, H, roughness);
|
||||||
float G = GeometrySmith(N, V, L, roughness);
|
float G = GeometrySmith(N, V, L, roughness);
|
||||||
vec3 kD = (1.0 - F) * (1.0 - metallic);
|
vec3 kD = (1.0 - F) * (1.0 - metallic);
|
||||||
|
|
||||||
// ---- 3. DIRECT LIGHT (sun + shadow) ----
|
|
||||||
vec4 light_proj = mvp_shadow * vec4(floor(vpos * 16.0) / 16.0, 1.0);
|
vec4 light_proj = mvp_shadow * vec4(floor(vpos * 16.0) / 16.0, 1.0);
|
||||||
vec3 light_ndc = light_proj.xyz / light_proj.w * 0.5 + 0.5;
|
vec3 light_ndc = light_proj.xyz / light_proj.w * 0.5 + 0.5;
|
||||||
light_ndc.z -= 0.001;
|
light_ndc.z -= 0.001;
|
||||||
@ -477,82 +245,30 @@ void main() {
|
|||||||
vec3 direct_specular = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.0001);
|
vec3 direct_specular = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.0001);
|
||||||
vec3 light = shadow * (kD * albedo / PI + direct_specular) * NdotL * sunLightColor * sunIntensity;
|
vec3 light = shadow * (kD * albedo / PI + direct_specular) * NdotL * sunLightColor * sunIntensity;
|
||||||
|
|
||||||
// ---- 4. INDIRECT LIGHT (RDM / SH / ambient fallback) ----
|
float ssao = texture(sampler2D(ssaotex, linsmp),
|
||||||
ivec3 local = ivec3(mod(floor(trileCenter), 32.0));
|
gl_FragCoord.xy / vec2(float(screen_w), float(screen_h))).r;
|
||||||
vec4 atlas_rect = rdm_atlas_rect(local, roughnessInt);
|
vec3 emissive = albedo * emittance * emissive_scale;
|
||||||
float ssao = texture(sampler2D(ssaotex, rdmsmp),
|
|
||||||
gl_FragCoord.xy / vec2(float(screen_w), float(screen_h))).r;
|
|
||||||
vec3 emissive = albedo * emittance * emissive_scale;
|
|
||||||
|
|
||||||
if (rdm_enabled == 1) {
|
vec3 Frough = FresnelSchlickRoughness(NdotV, F0, roughness);
|
||||||
vec3 Frough = FresnelSchlickRoughness(NdotV, F0, roughness);
|
|
||||||
vec3 hemispherePos = trileCenter + N * 0.49;
|
|
||||||
vec3 diff = vpos - hemispherePos;
|
|
||||||
|
|
||||||
// 4a. Indirect specular.
|
if (roughness < ROUGHNESS_SPEC_CUTOFF) {
|
||||||
// roughnessInt 0-1 with a baked RDM: ray-march or single-sample into the atlas.
|
vec3 R = reflect(-V, N);
|
||||||
// roughnessInt 2+ without RDM data: sky reflection.
|
vec2 envBRDF = texture(sampler2D(brdf_lut, linsmp), vec2(NdotV, roughness)).rg;
|
||||||
// roughnessInt > ROUGHNESS_SPEC_CUTOFF: skip specular entirely.
|
float specRoughFd = 1.0 - clamp((roughness - 0.5) / 0.3, 0.0, 1.0);
|
||||||
if (roughnessInt <= 1 && atlas_rect.z > 0.0) {
|
light += sky_reflect(R, sunPosition) * (Frough * envBRDF.x + envBRDF.y)
|
||||||
int face = rdm_face_from_normal(N);
|
* indirect_spec_scale * specRoughFd;
|
||||||
ivec2 atlasSize = textureSize(sampler2D(rdm_atlas, rdmsmp), 0);
|
|
||||||
vec2 atlasInvSz = 1.0 / vec2(atlasSize);
|
|
||||||
int rdmSize = int(atlas_rect.z * float(atlasSize.x)) / 2;
|
|
||||||
ivec2 fOff = rdm_face_offset(atlas_rect, face, rdmSize, atlasSize);
|
|
||||||
|
|
||||||
vec3 indirectSpec = roughness < ROUGHNESS_RAYMARCH_MAX
|
|
||||||
? rdm_spec_raymarch(N, -cv, diff, face, fOff, rdmSize, atlasInvSz)
|
|
||||||
: rdm_spec_single (N, -cv, diff, face, fOff, rdmSize, atlasInvSz);
|
|
||||||
indirectSpec *= rdm_tint;
|
|
||||||
|
|
||||||
float specLum = dot(indirectSpec, vec3(0.2126, 0.7152, 0.0722));
|
|
||||||
indirectSpec = mix(indirectSpec, vec3(specLum), metallic);
|
|
||||||
|
|
||||||
vec2 envBRDF = texture(sampler2D(brdf_lut, rdmsmp), vec2(NdotV, roughness)).rg;
|
|
||||||
float roughnessBell = 1.0 - 0.7 * sin(roughness * PI);
|
|
||||||
float grazingSuppr = 1.0 - 0.9 * roughness * sin(roughness * PI) * pow(1.0 - NdotV, 2.0);
|
|
||||||
float specRoughFade = 1.0 - clamp((roughness - 0.5) / 0.3, 0.0, 1.0);
|
|
||||||
|
|
||||||
light += indirectSpec * (Frough * envBRDF.x + envBRDF.y)
|
|
||||||
* rdm_spec_scale * roughnessBell * grazingSuppr * specRoughFade;
|
|
||||||
} else if (roughness < ROUGHNESS_SPEC_CUTOFF) {
|
|
||||||
vec3 R = reflect(-V, N);
|
|
||||||
vec2 envBRDF = texture(sampler2D(brdf_lut, rdmsmp), vec2(NdotV, roughness)).rg;
|
|
||||||
float specRoughFd = 1.0 - clamp((roughness - 0.5) / 0.3, 0.0, 1.0);
|
|
||||||
light += sky_reflect(R, sunPosition) * (Frough * envBRDF.x + envBRDF.y)
|
|
||||||
* rdm_spec_scale * specRoughFd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4b. Indirect diffuse.
|
|
||||||
// SH probe grid when available (trilinear, confidence-weighted).
|
|
||||||
// Falls back to RDM level-7 diffuse probes.
|
|
||||||
vec3 indirectDiff;
|
|
||||||
if (sh_enabled == 1) {
|
|
||||||
vec3 trile_origin = floor(trileCenter);
|
|
||||||
vec3 local_frag = vec3(local) + (vpos - trile_origin);
|
|
||||||
vec3 probe_f = clamp(local_frag * 2.0, vec3(0.0), vec3(63.0));
|
|
||||||
ivec3 p0 = ivec3(floor(probe_f));
|
|
||||||
ivec3 p1 = min(p0 + ivec3(1), ivec3(63));
|
|
||||||
indirectDiff = sh_eval_trilinear(p0, p1, fract(probe_f), N) * rdm_tint;
|
|
||||||
} else {
|
|
||||||
indirectDiff = rdm_indirect_diffuse(N, diff, local) * rdm_tint;
|
|
||||||
}
|
|
||||||
float diffLuma = dot(indirectDiff, vec3(0.2126, 0.7152, 0.0722));
|
|
||||||
indirectDiff = mix(vec3(diffLuma), indirectDiff, rdm_diff_saturation);
|
|
||||||
|
|
||||||
light += (1.0 - Frough) * (1.0 - metallic) * indirectDiff / PI * albedo * ssao * rdm_diff_scale;
|
|
||||||
|
|
||||||
// 4c. Ambient floor — kicks in when indirect light is below the configured minimum.
|
|
||||||
if (rdm_diff_scale < 0.001 || length(light) < ambient_intensity)
|
|
||||||
light += ambient_color * max(ambient_intensity - length(light), 0.0) * albedo * ssao;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// No baked data: flat ambient + sky specular.
|
|
||||||
light += ambient_color * ambient_intensity * albedo * ssao;
|
|
||||||
light += F * sky_reflect(reflect(-V, N), sunPosition) * 0.1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 5. FINAL COMPOSITE ----
|
vec3 indirectDiff;
|
||||||
|
if (sh_enabled == 1) {
|
||||||
|
vec2 sh_uv = gl_FragCoord.xy / vec2(float(screen_w), float(screen_h));
|
||||||
|
indirectDiff = texture(sampler2D(sh_irradiance, linsmp), sh_uv).rgb * indirect_tint;
|
||||||
|
} else {
|
||||||
|
indirectDiff = ambient_color * ambient_intensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
light += (1.0 - Frough) * (1.0 - metallic) * indirectDiff / PI * albedo * ssao * indirect_diff_scale;
|
||||||
|
|
||||||
vec3 final_color = light + emissive;
|
vec3 final_color = light + emissive;
|
||||||
frag_color = vec4(mix(deepColor, final_color, smoothstep(0.0, planeHeight, vpos.y)), 1.0);
|
frag_color = vec4(mix(deepColor, final_color, smoothstep(0.0, planeHeight, vpos.y)), 1.0);
|
||||||
|
|
||||||
|
|||||||
346
src/shaders/shader_trile_rdm.glsl
Normal file
346
src/shaders/shader_trile_rdm.glsl
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
@vs vs_trile_rdm
|
||||||
|
|
||||||
|
in vec4 position;
|
||||||
|
in vec4 normal;
|
||||||
|
in vec4 centre;
|
||||||
|
in vec4 instance;
|
||||||
|
|
||||||
|
layout(binding=0) uniform trile_rdm_vs_params {
|
||||||
|
mat4 mvp;
|
||||||
|
mat4 mvp_shadow;
|
||||||
|
vec3 camera;
|
||||||
|
};
|
||||||
|
|
||||||
|
out vec3 cam;
|
||||||
|
out vec3 to_center;
|
||||||
|
out vec3 vpos;
|
||||||
|
out vec3 ipos;
|
||||||
|
out vec4 fnormal;
|
||||||
|
out vec3 orig_normal;
|
||||||
|
out vec3 trileCenter;
|
||||||
|
out vec3 cv;
|
||||||
|
|
||||||
|
mat3 rot_x(float a) { float c=cos(a),s=sin(a); return mat3(1,0,0, 0,c,-s, 0,s,c); }
|
||||||
|
mat3 rot_y(float a) { float c=cos(a),s=sin(a); return mat3(c,0,s, 0,1,0, -s,0,c); }
|
||||||
|
mat3 rot_z(float a) { float c=cos(a),s=sin(a); return mat3(c,-s,0, s,c,0, 0,0,1); }
|
||||||
|
|
||||||
|
mat3 get_orientation_matrix(int ori) {
|
||||||
|
int face = ori / 4;
|
||||||
|
int twist = ori % 4;
|
||||||
|
float PI = 3.1415927;
|
||||||
|
mat3 base;
|
||||||
|
if (face == 0) base = mat3(1.0);
|
||||||
|
else if (face == 1) base = rot_x(PI);
|
||||||
|
else if (face == 2) base = rot_z(-PI*0.5);
|
||||||
|
else if (face == 3) base = rot_z( PI*0.5);
|
||||||
|
else if (face == 4) base = rot_x( PI*0.5);
|
||||||
|
else base = rot_x(-PI*0.5);
|
||||||
|
return base * rot_y(float(twist) * PI * 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
int ori = int(round(instance.w));
|
||||||
|
mat3 rot = get_orientation_matrix(ori);
|
||||||
|
vec3 local = position.xyz - 0.5;
|
||||||
|
vec3 rotated = rot * local + 0.5;
|
||||||
|
|
||||||
|
gl_Position = mvp * vec4(rotated + instance.xyz, 1.0);
|
||||||
|
fnormal = vec4(rot * normal.xyz, 0.0);
|
||||||
|
orig_normal = normal.xyz;
|
||||||
|
to_center = centre.xyz - position.xyz;
|
||||||
|
vpos = rotated + instance.xyz;
|
||||||
|
ipos = position.xyz;
|
||||||
|
cam = camera;
|
||||||
|
cv = normalize(camera - vpos);
|
||||||
|
trileCenter = instance.xyz + vec3(0.5);
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@fs fs_trile_rdm
|
||||||
|
|
||||||
|
layout(binding=1) uniform trile_rdm_world_config {
|
||||||
|
vec3 skyBase;
|
||||||
|
vec3 skyTop;
|
||||||
|
vec3 sunDisk;
|
||||||
|
vec3 horizonHalo;
|
||||||
|
vec3 sunHalo;
|
||||||
|
vec3 sunLightColor;
|
||||||
|
vec3 sunPosition;
|
||||||
|
float sunIntensity;
|
||||||
|
float skyIntensity;
|
||||||
|
|
||||||
|
int hasClouds;
|
||||||
|
|
||||||
|
float planeHeight;
|
||||||
|
int animatePlaneHeight;
|
||||||
|
vec3 waterColor;
|
||||||
|
vec3 deepColor;
|
||||||
|
|
||||||
|
float time;
|
||||||
|
int hsv_lighting;
|
||||||
|
};
|
||||||
|
|
||||||
|
in vec3 cam;
|
||||||
|
in vec3 to_center;
|
||||||
|
in vec3 vpos;
|
||||||
|
in vec3 ipos;
|
||||||
|
in vec4 fnormal;
|
||||||
|
in vec3 orig_normal;
|
||||||
|
in vec3 trileCenter;
|
||||||
|
in vec3 cv;
|
||||||
|
out vec4 frag_color;
|
||||||
|
|
||||||
|
layout(binding=3) uniform trile_rdm_fs_params {
|
||||||
|
mat4 mvp_shadow;
|
||||||
|
int is_reflection;
|
||||||
|
int screen_h;
|
||||||
|
int screen_w;
|
||||||
|
float ambient_intensity;
|
||||||
|
float emissive_scale;
|
||||||
|
float indirect_diff_scale;
|
||||||
|
float indirect_spec_scale;
|
||||||
|
vec3 ambient_color;
|
||||||
|
int is_preview;
|
||||||
|
vec3 indirect_tint;
|
||||||
|
int sh_enabled;
|
||||||
|
vec4 atlas_rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(binding = 0) uniform texture2D rdm_triletex;
|
||||||
|
layout(binding = 0) uniform sampler rdm_trilesmp;
|
||||||
|
layout(binding = 1) uniform texture2D rdm_ssaotex;
|
||||||
|
layout(binding = 2) uniform texture2D rdm_shadowtex;
|
||||||
|
layout(binding = 2) uniform sampler rdm_shadowsmp;
|
||||||
|
layout(binding = 3) uniform texture2D rdm_brdflut;
|
||||||
|
layout(binding = 4) uniform texture2D rdm_shirradiance;
|
||||||
|
layout(binding = 5) uniform texture2D rdm_atlas;
|
||||||
|
layout(binding = 3) uniform sampler rdm_linsmp;
|
||||||
|
|
||||||
|
const float PI = 3.1415927;
|
||||||
|
const float ROUGHNESS_SPEC_CUTOFF = 0.7;
|
||||||
|
const float ROUGHNESS_RAYMARCH_MAX = 0.2;
|
||||||
|
|
||||||
|
vec3 sky(vec3 skypos, vec3 sunpos) {
|
||||||
|
vec3 npos = normalize(skypos);
|
||||||
|
float sDist = dot(npos, normalize(sunpos));
|
||||||
|
|
||||||
|
vec3 skyGradient = mix(skyBase, skyTop, clamp(npos.y * 2.0, 0.0, 0.7));
|
||||||
|
vec3 result = skyGradient;
|
||||||
|
|
||||||
|
result += sunHalo * clamp((sDist - 0.95) * 10.0, 0.0, 0.8) * 0.2;
|
||||||
|
|
||||||
|
if (sDist > 0.9999)
|
||||||
|
result = sunDisk;
|
||||||
|
|
||||||
|
result += mix(horizonHalo, vec3(0.0), clamp(abs(npos.y) * 80.0, 0.0, 1.0)) * 0.1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 sky_reflect(vec3 R, vec3 sunpos) {
|
||||||
|
if (R.y < 0.0) R = reflect(R, vec3(0.0, 1.0, 0.0));
|
||||||
|
return sky(R, sunpos);
|
||||||
|
}
|
||||||
|
|
||||||
|
float DistributionGGX(vec3 N, vec3 H, float roughness) {
|
||||||
|
float a = roughness * roughness;
|
||||||
|
float a2 = a * a;
|
||||||
|
float NdotH = max(dot(N, H), 0.0);
|
||||||
|
float denom = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
||||||
|
return a2 / (PI * denom * denom);
|
||||||
|
}
|
||||||
|
|
||||||
|
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
|
||||||
|
float r = roughness + 1.0;
|
||||||
|
float k = (r * r) / 8.0;
|
||||||
|
float NdotV = max(dot(N, V), 0.0);
|
||||||
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
|
float ggx1 = NdotL / (NdotL * (1.0 - k) + k);
|
||||||
|
float ggx2 = NdotV / (NdotV * (1.0 - k) + k);
|
||||||
|
return ggx1 * ggx2;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
|
||||||
|
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
|
||||||
|
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 rdm_hemioct(vec3 v, int face) {
|
||||||
|
vec3 vc = v;
|
||||||
|
if (face / 2 == 0) { vc.z = v.y; vc.y = v.z; }
|
||||||
|
if (face / 2 == 2) { vc.z = v.x; vc.x = v.z; }
|
||||||
|
if (face % 2 == 1) { vc.z *= -1.0; }
|
||||||
|
|
||||||
|
vec2 p = vc.xy * (1.0 / (abs(vc.x) + abs(vc.y) + vc.z));
|
||||||
|
return vec2(p.x + p.y, p.x - p.y) * 0.5 + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rdm_face_from_normal(vec3 N) {
|
||||||
|
vec3 a = abs(N);
|
||||||
|
if (a.y >= a.x && a.y >= a.z) return N.y >= 0.0 ? 0 : 1;
|
||||||
|
if (a.z >= a.x && a.z >= a.y) return N.z >= 0.0 ? 2 : 3;
|
||||||
|
return N.x >= 0.0 ? 4 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
ivec2 rdm_face_offset(vec4 rect, int face, int rdmSize, ivec2 atlasSize) {
|
||||||
|
int col = face % 2;
|
||||||
|
int row = face / 2;
|
||||||
|
return ivec2(int(rect.x * float(atlasSize.x)) + col * rdmSize,
|
||||||
|
int(rect.y * float(atlasSize.y)) + row * rdmSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 rdm_spec_raymarch(vec3 N, vec3 V, vec3 diff, int face, ivec2 faceOffset, int rdmSize, vec2 atlasInvSize) {
|
||||||
|
vec3 reflected = reflect(V, N);
|
||||||
|
float maxDist = 20.0;
|
||||||
|
int steps = 40;
|
||||||
|
float stepSize = maxDist / float(steps);
|
||||||
|
|
||||||
|
for (int i = 0; i < steps; i++) {
|
||||||
|
float t = stepSize * float(i + 1);
|
||||||
|
vec3 samplePos = diff + t * reflected;
|
||||||
|
if (dot(samplePos, N) < 0.0) continue;
|
||||||
|
|
||||||
|
vec3 dir = normalize(samplePos);
|
||||||
|
vec2 hemiUV = rdm_hemioct(dir, face);
|
||||||
|
vec2 texCoord = (vec2(faceOffset) + hemiUV * float(rdmSize)) * atlasInvSize;
|
||||||
|
vec4 s = texture(sampler2D(rdm_atlas, rdm_linsmp), texCoord, 0);
|
||||||
|
|
||||||
|
float dist = length(samplePos);
|
||||||
|
if (s.a > 0.0 && s.a < dist && s.a + stepSize > dist)
|
||||||
|
return s.rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sky_reflect(reflected, sunPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 rdm_spec_single(vec3 N, vec3 V, vec3 diff, int face, ivec2 faceOffset, int rdmSize, vec2 atlasInvSize) {
|
||||||
|
vec3 reflected = reflect(V, N);
|
||||||
|
vec3 sampleDir = normalize(diff + 2.0 * reflected);
|
||||||
|
vec2 hemiUV = rdm_hemioct(sampleDir, face);
|
||||||
|
vec2 texCoord = (vec2(faceOffset) + hemiUV * float(rdmSize)) * atlasInvSize;
|
||||||
|
return texture(sampler2D(rdm_atlas, rdm_linsmp), texCoord).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
if (vpos.y < planeHeight - 0.01 && is_reflection == 1) discard;
|
||||||
|
|
||||||
|
vec3 sample_pos = ipos - orig_normal * 0.02;
|
||||||
|
vec4 trixel_material;
|
||||||
|
int maxSteps = is_reflection == 1 ? 1 : 3;
|
||||||
|
for (int i = 0; i < maxSteps; i++) {
|
||||||
|
ivec2 texel = ivec2(
|
||||||
|
int(clamp(sample_pos.z, 0.0001, 0.99999) * 16.0),
|
||||||
|
int(clamp(sample_pos.y, 0.0001, 0.99999) * 16.0) +
|
||||||
|
int(clamp(sample_pos.x, 0.0001, 0.99999) * 16.0) * 16
|
||||||
|
);
|
||||||
|
trixel_material = texelFetch(sampler2D(rdm_triletex, rdm_trilesmp), texel, 0);
|
||||||
|
if (dot(trixel_material, trixel_material) > 0.0001) break;
|
||||||
|
sample_pos += to_center * 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 albedo = trixel_material.xyz;
|
||||||
|
|
||||||
|
int packed = int(round(trixel_material.w * 255.0));
|
||||||
|
float emittance = 0.0;
|
||||||
|
int roughnessInt = 0;
|
||||||
|
float roughness = 0.0;
|
||||||
|
float metallic = 0.0;
|
||||||
|
|
||||||
|
if ((packed & 0x1) != 0) {
|
||||||
|
emittance = float((packed >> 1) & 0x7F) / 127.0;
|
||||||
|
} else {
|
||||||
|
roughnessInt = (packed >> 5) & 0x7;
|
||||||
|
roughness = max(float(roughnessInt) / 7.0, 0.05);
|
||||||
|
metallic = float((packed >> 3) & 0x3) / 3.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 absN = abs(fnormal.xyz);
|
||||||
|
vec3 N;
|
||||||
|
if (absN.x >= absN.y && absN.x >= absN.z) N = vec3(sign(fnormal.x), 0.0, 0.0);
|
||||||
|
else if (absN.y >= absN.x && absN.y >= absN.z) N = vec3(0.0, sign(fnormal.y), 0.0);
|
||||||
|
else N = vec3(0.0, 0.0, sign(fnormal.z));
|
||||||
|
|
||||||
|
if (is_reflection == 1) {
|
||||||
|
vec3 L = normalize(sunPosition);
|
||||||
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
|
frag_color = vec4(albedo * (NdotL * sunLightColor * sunIntensity + 0.1), 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 V = normalize(cam - vpos);
|
||||||
|
vec3 L = normalize(sunPosition);
|
||||||
|
vec3 H = normalize(V + L);
|
||||||
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
|
float NdotV = max(dot(N, V), 0.0);
|
||||||
|
float HdotV = max(dot(H, V), 0.0);
|
||||||
|
|
||||||
|
vec3 F0 = mix(vec3(0.04), albedo, metallic);
|
||||||
|
vec3 F = fresnelSchlick(HdotV, F0);
|
||||||
|
float NDF = DistributionGGX(N, H, roughness);
|
||||||
|
float G = GeometrySmith(N, V, L, roughness);
|
||||||
|
vec3 kD = (1.0 - F) * (1.0 - metallic);
|
||||||
|
|
||||||
|
vec4 light_proj = mvp_shadow * vec4(floor(vpos * 16.0) / 16.0, 1.0);
|
||||||
|
vec3 light_ndc = light_proj.xyz / light_proj.w * 0.5 + 0.5;
|
||||||
|
light_ndc.z -= 0.001;
|
||||||
|
float shadow = texture(sampler2DShadow(rdm_shadowtex, rdm_shadowsmp), light_ndc);
|
||||||
|
|
||||||
|
vec3 direct_specular = (NDF * G * F) / (4.0 * NdotV * NdotL + 0.0001);
|
||||||
|
vec3 light = shadow * (kD * albedo / PI + direct_specular) * NdotL * sunLightColor * sunIntensity;
|
||||||
|
|
||||||
|
float ssao = texture(sampler2D(rdm_ssaotex, rdm_linsmp),
|
||||||
|
gl_FragCoord.xy / vec2(float(screen_w), float(screen_h))).r;
|
||||||
|
vec3 emissive = albedo * emittance * emissive_scale;
|
||||||
|
|
||||||
|
vec3 Frough = FresnelSchlickRoughness(NdotV, F0, roughness);
|
||||||
|
|
||||||
|
vec3 hemispherePos = trileCenter + N * 0.49;
|
||||||
|
vec3 diff = vpos - hemispherePos;
|
||||||
|
|
||||||
|
if (roughnessInt <= 1) {
|
||||||
|
int face = rdm_face_from_normal(N);
|
||||||
|
ivec2 atlasSize = textureSize(sampler2D(rdm_atlas, rdm_linsmp), 0);
|
||||||
|
vec2 atlasInvSz = 1.0 / vec2(atlasSize);
|
||||||
|
int rdmSize = int(atlas_rect.z * float(atlasSize.x)) / 2;
|
||||||
|
ivec2 fOff = rdm_face_offset(atlas_rect, face, rdmSize, atlasSize);
|
||||||
|
|
||||||
|
vec3 indirectSpec = roughness < ROUGHNESS_RAYMARCH_MAX
|
||||||
|
? rdm_spec_raymarch(N, -cv, diff, face, fOff, rdmSize, atlasInvSz)
|
||||||
|
: rdm_spec_single (N, -cv, diff, face, fOff, rdmSize, atlasInvSz);
|
||||||
|
|
||||||
|
vec2 envBRDF = texture(sampler2D(rdm_brdflut, rdm_linsmp), vec2(NdotV, roughness)).rg;
|
||||||
|
float roughnessBell = 1.0 - 0.7 * sin(roughness * PI);
|
||||||
|
float grazingSuppr = 1.0 - 0.9 * roughness * sin(roughness * PI) * pow(1.0 - NdotV, 2.0);
|
||||||
|
float specRoughFade = 1.0 - clamp((roughness - 0.5) / 0.3, 0.0, 1.0);
|
||||||
|
|
||||||
|
light += indirectSpec * (Frough * envBRDF.x + envBRDF.y)
|
||||||
|
* indirect_spec_scale * roughnessBell * grazingSuppr * specRoughFade;
|
||||||
|
} else if (roughness < ROUGHNESS_SPEC_CUTOFF) {
|
||||||
|
vec3 R = reflect(-V, N);
|
||||||
|
vec2 envBRDF = texture(sampler2D(rdm_brdflut, rdm_linsmp), vec2(NdotV, roughness)).rg;
|
||||||
|
float specRoughFd = 1.0 - clamp((roughness - 0.5) / 0.3, 0.0, 1.0);
|
||||||
|
light += sky_reflect(R, sunPosition) * (Frough * envBRDF.x + envBRDF.y)
|
||||||
|
* indirect_spec_scale * specRoughFd;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 indirectDiff;
|
||||||
|
if (sh_enabled == 1) {
|
||||||
|
vec2 sh_uv = gl_FragCoord.xy / vec2(float(screen_w), float(screen_h));
|
||||||
|
indirectDiff = texture(sampler2D(rdm_shirradiance, rdm_linsmp), sh_uv).rgb * indirect_tint;
|
||||||
|
} else {
|
||||||
|
indirectDiff = ambient_color * ambient_intensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
light += (1.0 - Frough) * (1.0 - metallic) * indirectDiff / PI * albedo * ssao * indirect_diff_scale;
|
||||||
|
|
||||||
|
vec3 final_color = light + emissive;
|
||||||
|
frag_color = vec4(mix(deepColor, final_color, smoothstep(0.0, planeHeight, vpos.y)), 1.0);
|
||||||
|
|
||||||
|
if (is_preview == 1) frag_color.rgb = mix(frag_color.rgb, vec3(0.3, 0.7, 1.0), 0.5);
|
||||||
|
else if (is_preview == 2) frag_color.rgb = mix(frag_color.rgb, vec3(1.0, 0.3, 0.2), 0.5);
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@program trile_rdm vs_trile_rdm fs_trile_rdm
|
||||||
@ -121,23 +121,6 @@ delete_trile :: (name: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_trile_roughness_set :: (trile_name: string) -> u8 {
|
|
||||||
trile, ok := get_trile(trile_name);
|
|
||||||
if !ok then return 1 << 7;
|
|
||||||
mask : u8 = 1 << 7;
|
|
||||||
for x: 0..15 {
|
|
||||||
for y: 0..15 {
|
|
||||||
for z: 0..15 {
|
|
||||||
if !trile.trixels[x][y][z].empty {
|
|
||||||
mask |= cast(u8)(1 << trile.trixels[x][y][z].material.roughness);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
lstrile :: () -> string {
|
lstrile :: () -> string {
|
||||||
count := 0;
|
count := 0;
|
||||||
for v : trile_table {
|
for v : trile_table {
|
||||||
|
|||||||
@ -17,15 +17,15 @@ demo_toggle_float :: (val: *float, saved: *float, default: float) {
|
|||||||
draw_demo_ui :: (theme: *GR.Overall_Theme) {
|
draw_demo_ui :: (theme: *GR.Overall_Theme) {
|
||||||
r := GR.get_rect(ui_w(86,0), ui_h(1,0), ui_w(13,0), ui_h(3,0));
|
r := GR.get_rect(ui_w(86,0), ui_h(1,0), ui_w(13,0), ui_h(3,0));
|
||||||
|
|
||||||
rdm_diff_on := current_lighting_config.rdm_diff_scale > 0;
|
diff_on := current_lighting_config.indirect_diff_scale > 0;
|
||||||
if GR.button(r, ifx rdm_diff_on then "RDM Diffuse: ON" else "RDM Diffuse: OFF") {
|
if GR.button(r, ifx diff_on then "Indirect Diffuse: ON" else "Indirect Diffuse: OFF") {
|
||||||
demo_toggle_float(*current_lighting_config.rdm_diff_scale, *demo_saved_rdm_diff, 1.0);
|
demo_toggle_float(*current_lighting_config.indirect_diff_scale, *demo_saved_rdm_diff, 1.0);
|
||||||
}
|
}
|
||||||
r.y += r.h * 1.3;
|
r.y += r.h * 1.3;
|
||||||
|
|
||||||
rdm_spec_on := current_lighting_config.rdm_spec_scale > 0;
|
spec_on := current_lighting_config.indirect_spec_scale > 0;
|
||||||
if GR.button(r, ifx rdm_spec_on then "RDM Specular: ON" else "RDM Specular: OFF") {
|
if GR.button(r, ifx spec_on then "Indirect Specular: ON" else "Indirect Specular: OFF") {
|
||||||
demo_toggle_float(*current_lighting_config.rdm_spec_scale, *demo_saved_rdm_spec, 1.0);
|
demo_toggle_float(*current_lighting_config.indirect_spec_scale, *demo_saved_rdm_spec, 1.0);
|
||||||
}
|
}
|
||||||
r.y += r.h * 1.3;
|
r.y += r.h * 1.3;
|
||||||
|
|
||||||
|
|||||||
@ -429,14 +429,15 @@ font_boundary :: () {
|
|||||||
render_ui :: () {
|
render_ui :: () {
|
||||||
voxel_theme := get_voxel_theme();
|
voxel_theme := get_voxel_theme();
|
||||||
GR.set_default_theme(voxel_theme);
|
GR.set_default_theme(voxel_theme);
|
||||||
|
loading := show_loading_screen();
|
||||||
#if !FLAG_RELEASE_BUILD {
|
#if !FLAG_RELEASE_BUILD {
|
||||||
draw_editor_ui(*voxel_theme);
|
draw_editor_ui(*voxel_theme);
|
||||||
if !in_editor_view then game_ui(*voxel_theme);
|
if !in_editor_view && !loading then game_ui(*voxel_theme);
|
||||||
draw_console(*voxel_theme);
|
draw_console(*voxel_theme);
|
||||||
} else {
|
} else {
|
||||||
game_ui(*voxel_theme);
|
if !loading then game_ui(*voxel_theme);
|
||||||
}
|
}
|
||||||
#if FLAG_DEMO_BUILD { if !in_editor_view then draw_demo_ui(*voxel_theme); }
|
#if FLAG_DEMO_BUILD { if !in_editor_view && !loading then draw_demo_ui(*voxel_theme); }
|
||||||
draw_settings_menu();
|
draw_settings_menu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -92,6 +92,8 @@ get_voxel_theme :: () -> GR.Overall_Theme {
|
|||||||
t.slider_theme.background.frame_color = cfg.frame_color;
|
t.slider_theme.background.frame_color = cfg.frame_color;
|
||||||
t.slider_theme.background.frame_color_over = cfg.frame_color;
|
t.slider_theme.background.frame_color_over = cfg.frame_color;
|
||||||
|
|
||||||
|
t.slider_theme.foreground.label_theme.text_color = .{1, 1, 1, 1};
|
||||||
|
|
||||||
apply_voxel_btn(*t.slider_theme.spinbox_theme, cfg, true);
|
apply_voxel_btn(*t.slider_theme.spinbox_theme, cfg, true);
|
||||||
|
|
||||||
t.slider_theme.surface_style = .EXTEND_FROM_LEFT;
|
t.slider_theme.surface_style = .EXTEND_FROM_LEFT;
|
||||||
|
|||||||
142
src/world.jai
142
src/world.jai
@ -52,21 +52,9 @@ Chunk :: struct {
|
|||||||
coord: Chunk_Key;
|
coord: Chunk_Key;
|
||||||
groups: [..]Chunk_Trile_Group;
|
groups: [..]Chunk_Trile_Group;
|
||||||
|
|
||||||
rdm_atlas: sg_image;
|
|
||||||
rdm_lookup: sg_image;
|
|
||||||
rdm_valid: bool;
|
|
||||||
rdm_dirty: bool;
|
|
||||||
rdm_atlas_path: string;
|
|
||||||
rdm_lookup_path: string;
|
|
||||||
|
|
||||||
sh_probe_grid: sg_image; // 192x4096 RGBA16F 2D texture (2 probes/trile/axis)
|
sh_probe_grid: sg_image; // 192x4096 RGBA16F 2D texture (2 probes/trile/axis)
|
||||||
sh_valid: bool;
|
sh_valid: bool;
|
||||||
sh_dirty: bool;
|
sh_dirty: bool;
|
||||||
#if !FLAG_RELEASE_BUILD {
|
|
||||||
rdm_lookup_cpu: []float;
|
|
||||||
rdm_lookup_w: s32;
|
|
||||||
rdm_lookup_h: s32;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Editor_Note :: struct {
|
Editor_Note :: struct {
|
||||||
@ -74,12 +62,20 @@ Editor_Note :: struct {
|
|||||||
text : string;
|
text : string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RDM_DEFAULT_SIZE :: 128;
|
||||||
|
|
||||||
Rdm_Instance_Override :: struct {
|
Rdm_Instance_Override :: struct {
|
||||||
x : s32;
|
x : s32;
|
||||||
y : s32;
|
y : s32;
|
||||||
z : s32;
|
z : s32;
|
||||||
size_override : s32;
|
rdm_enabled : bool;
|
||||||
quality_override : s32;
|
rdm_size : s32; // side in px; 0 means legacy — treat as RDM_DEFAULT_SIZE.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-world entry of the global RDM atlas. atlas_rect is normalized UV (x, y, w, h).
|
||||||
|
Rdm_Atlas_Entry :: struct {
|
||||||
|
x, y, z : s32;
|
||||||
|
atlas_rect : Vector4;
|
||||||
}
|
}
|
||||||
|
|
||||||
World :: struct {
|
World :: struct {
|
||||||
@ -89,32 +85,72 @@ World :: struct {
|
|||||||
emitter_instances : [..]Particle_Emitter_Instance;
|
emitter_instances : [..]Particle_Emitter_Instance;
|
||||||
notes : [..]Editor_Note;
|
notes : [..]Editor_Note;
|
||||||
rdm_overrides : [..]Rdm_Instance_Override;
|
rdm_overrides : [..]Rdm_Instance_Override;
|
||||||
|
rdm_lookup : [..]Rdm_Atlas_Entry; // populated by bake (Step 5) and by loader (Step 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
get_rdm_instance_override :: (world: *World, x: s32, y: s32, z: s32) -> (size_override: s32, quality_override: s32) {
|
rdm_get_atlas_rect :: (world: *World, x: s32, y: s32, z: s32) -> (Vector4, bool) {
|
||||||
for world.rdm_overrides {
|
for world.rdm_lookup {
|
||||||
if it.x == x && it.y == y && it.z == z then return it.size_override, it.quality_override;
|
if it.x == x && it.y == y && it.z == z then return it.atlas_rect, true;
|
||||||
}
|
}
|
||||||
return 0, 0;
|
return .{}, false;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_rdm_instance_override :: (world: *World, x: s32, y: s32, z: s32, size_override: s32, quality_override: s32) {
|
is_rdm_instance_enabled :: (world: *World, x: s32, y: s32, z: s32) -> bool {
|
||||||
|
for world.rdm_overrides {
|
||||||
|
if it.x == x && it.y == y && it.z == z then return it.rdm_enabled;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_rdm_instance_enabled :: (world: *World, x: s32, y: s32, z: s32, enabled: bool) {
|
||||||
for *world.rdm_overrides {
|
for *world.rdm_overrides {
|
||||||
if it.x == x && it.y == y && it.z == z {
|
if it.x == x && it.y == y && it.z == z {
|
||||||
if size_override == 0 && quality_override == 0 {
|
if !enabled {
|
||||||
array_ordered_remove_by_index(*world.rdm_overrides, it_index);
|
array_ordered_remove_by_index(*world.rdm_overrides, it_index);
|
||||||
} else {
|
} else {
|
||||||
it.size_override = size_override;
|
it.rdm_enabled = true;
|
||||||
it.quality_override = quality_override;
|
if it.rdm_size == 0 then it.rdm_size = RDM_DEFAULT_SIZE;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if size_override != 0 || quality_override != 0 {
|
if enabled {
|
||||||
array_add(*world.rdm_overrides, .{x=x, y=y, z=z, size_override=size_override, quality_override=quality_override});
|
array_add(*world.rdm_overrides, .{x=x, y=y, z=z, rdm_enabled=true, rdm_size=RDM_DEFAULT_SIZE});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_rdm_instance_size :: (world: *World, x: s32, y: s32, z: s32) -> s32 {
|
||||||
|
for world.rdm_overrides {
|
||||||
|
if it.x == x && it.y == y && it.z == z {
|
||||||
|
if !it.rdm_enabled then return 0;
|
||||||
|
return ifx it.rdm_size > 0 then it.rdm_size else RDM_DEFAULT_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_rdm_instance_size :: (world: *World, x: s32, y: s32, z: s32, size: s32) {
|
||||||
|
for *world.rdm_overrides {
|
||||||
|
if it.x == x && it.y == y && it.z == z {
|
||||||
|
it.rdm_size = size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RDM_SIZE_LADDER :: s32.[32, 64, 128, 256, 512, 1024, 2048, 4096];
|
||||||
|
|
||||||
|
rdm_cycle_size :: (cur: s32, dir: s32) -> s32 {
|
||||||
|
idx : s32 = 2;
|
||||||
|
for RDM_SIZE_LADDER {
|
||||||
|
if it == cur { idx = cast(s32) it_index; break; }
|
||||||
|
}
|
||||||
|
idx += dir;
|
||||||
|
if idx < 0 then idx = 0;
|
||||||
|
if idx >= cast(s32) RDM_SIZE_LADDER.count then idx = cast(s32) RDM_SIZE_LADDER.count - 1;
|
||||||
|
return RDM_SIZE_LADDER[idx];
|
||||||
|
}
|
||||||
|
|
||||||
// Convert a world-space integer position to chunk coordinate.
|
// Convert a world-space integer position to chunk coordinate.
|
||||||
world_to_chunk_coord :: (wx: s32, wy: s32, wz: s32) -> Chunk_Key {
|
world_to_chunk_coord :: (wx: s32, wy: s32, wz: s32) -> Chunk_Key {
|
||||||
return .{
|
return .{
|
||||||
@ -172,23 +208,13 @@ unload_current_world :: () {
|
|||||||
if !current_world.valid then return;
|
if !current_world.valid then return;
|
||||||
rdm_loader_cancel_all();
|
rdm_loader_cancel_all();
|
||||||
for *chunk: current_world.world.chunks {
|
for *chunk: current_world.world.chunks {
|
||||||
if chunk.rdm_valid {
|
|
||||||
sg_destroy_image(chunk.rdm_atlas);
|
|
||||||
sg_destroy_image(chunk.rdm_lookup);
|
|
||||||
#if !FLAG_RELEASE_BUILD {
|
|
||||||
if chunk.rdm_lookup_cpu.data then free(chunk.rdm_lookup_cpu.data);
|
|
||||||
chunk.rdm_lookup_cpu = .{};
|
|
||||||
}
|
|
||||||
chunk.rdm_atlas = .{};
|
|
||||||
chunk.rdm_lookup = .{};
|
|
||||||
chunk.rdm_valid = false;
|
|
||||||
}
|
|
||||||
for *group: chunk.groups {
|
for *group: chunk.groups {
|
||||||
array_free(group.instances);
|
array_free(group.instances);
|
||||||
}
|
}
|
||||||
array_free(chunk.groups);
|
array_free(chunk.groups);
|
||||||
}
|
}
|
||||||
deinit(*current_world.world.chunks);
|
deinit(*current_world.world.chunks);
|
||||||
|
array_free(current_world.world.rdm_lookup);
|
||||||
Pool.reset(*current_world.pool);
|
Pool.reset(*current_world.pool);
|
||||||
current_world.valid = false;
|
current_world.valid = false;
|
||||||
}
|
}
|
||||||
@ -253,7 +279,6 @@ World_Json_Config :: struct {
|
|||||||
waterColor : [3]float;
|
waterColor : [3]float;
|
||||||
deepColor : [3]float;
|
deepColor : [3]float;
|
||||||
waterShininess : float;
|
waterShininess : float;
|
||||||
rdmDiffSaturation : float;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
World_Json_Chunk :: struct {
|
World_Json_Chunk :: struct {
|
||||||
@ -262,8 +287,6 @@ World_Json_Chunk :: struct {
|
|||||||
z : s32;
|
z : s32;
|
||||||
offset : s32;
|
offset : s32;
|
||||||
size : s32;
|
size : s32;
|
||||||
rdm_atlas : string;
|
|
||||||
rdm_lookup : string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
World_Json_Emitter :: struct {
|
World_Json_Emitter :: struct {
|
||||||
@ -278,11 +301,11 @@ World_Json_Note :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
World_Json_Rdm_Override :: struct {
|
World_Json_Rdm_Override :: struct {
|
||||||
x : s32;
|
x : s32;
|
||||||
y : s32;
|
y : s32;
|
||||||
z : s32;
|
z : s32;
|
||||||
size_override : s32;
|
rdm_enabled : bool;
|
||||||
quality_override : s32;
|
rdm_size : s32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// World_Config serialized as a fixed-size binary blob.
|
// World_Config serialized as a fixed-size binary blob.
|
||||||
@ -303,7 +326,6 @@ World_Config_Binary :: struct {
|
|||||||
water_color: [3]float;
|
water_color: [3]float;
|
||||||
deep_color: [3]float;
|
deep_color: [3]float;
|
||||||
water_shininess: float;
|
water_shininess: float;
|
||||||
rdm_diff_saturation: float;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
world_config_to_binary :: (conf: *World_Config) -> World_Config_Binary {
|
world_config_to_binary :: (conf: *World_Config) -> World_Config_Binary {
|
||||||
@ -323,7 +345,6 @@ world_config_to_binary :: (conf: *World_Config) -> World_Config_Binary {
|
|||||||
b.water_color = conf.waterColor.component;
|
b.water_color = conf.waterColor.component;
|
||||||
b.deep_color = conf.deepColor.component;
|
b.deep_color = conf.deepColor.component;
|
||||||
b.water_shininess = conf.waterShininess;
|
b.water_shininess = conf.waterShininess;
|
||||||
b.rdm_diff_saturation = conf.rdmDiffSaturation;
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +365,6 @@ world_config_from_binary :: (b: *World_Config_Binary) -> World_Config {
|
|||||||
conf.waterColor.component = b.water_color;
|
conf.waterColor.component = b.water_color;
|
||||||
conf.deepColor.component = b.deep_color;
|
conf.deepColor.component = b.deep_color;
|
||||||
conf.waterShininess = b.water_shininess;
|
conf.waterShininess = b.water_shininess;
|
||||||
conf.rdmDiffSaturation = b.rdm_diff_saturation;
|
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +429,6 @@ world_config_to_json :: (conf: *World_Config) -> World_Json_Config {
|
|||||||
jc.waterColor = conf.waterColor.component;
|
jc.waterColor = conf.waterColor.component;
|
||||||
jc.deepColor = conf.deepColor.component;
|
jc.deepColor = conf.deepColor.component;
|
||||||
jc.waterShininess = conf.waterShininess;
|
jc.waterShininess = conf.waterShininess;
|
||||||
jc.rdmDiffSaturation = conf.rdmDiffSaturation;
|
|
||||||
return jc;
|
return jc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +449,6 @@ world_config_from_json :: (jc: *World_Json_Config) -> World_Config {
|
|||||||
conf.waterColor.component = jc.waterColor;
|
conf.waterColor.component = jc.waterColor;
|
||||||
conf.deepColor.component = jc.deepColor;
|
conf.deepColor.component = jc.deepColor;
|
||||||
conf.waterShininess = ifx jc.waterShininess > 0 then jc.waterShininess else 64.0;
|
conf.waterShininess = ifx jc.waterShininess > 0 then jc.waterShininess else 64.0;
|
||||||
conf.rdmDiffSaturation = ifx jc.rdmDiffSaturation > 0 then jc.rdmDiffSaturation else 1.0;
|
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,8 +459,6 @@ save_world :: (world: *World) -> (json: string, chunks_bin: string) {
|
|||||||
coord: Chunk_Key;
|
coord: Chunk_Key;
|
||||||
offset: s32;
|
offset: s32;
|
||||||
size: s32;
|
size: s32;
|
||||||
rdm_atlas_path: string;
|
|
||||||
rdm_lookup_path: string;
|
|
||||||
}
|
}
|
||||||
chunk_entries: [..]Chunk_Save_Entry;
|
chunk_entries: [..]Chunk_Save_Entry;
|
||||||
chunk_entries.allocator = temp;
|
chunk_entries.allocator = temp;
|
||||||
@ -469,19 +485,10 @@ save_world :: (world: *World) -> (json: string, chunks_bin: string) {
|
|||||||
chunk_data := builder_to_string(*chunk_builder,, temp);
|
chunk_data := builder_to_string(*chunk_builder,, temp);
|
||||||
data_size := cast(s32) chunk_data.count;
|
data_size := cast(s32) chunk_data.count;
|
||||||
|
|
||||||
atlas_path := chunk.rdm_atlas_path;
|
|
||||||
lookup_path := chunk.rdm_lookup_path;
|
|
||||||
if !atlas_path.count {
|
|
||||||
atlas_path = tprint("%_%_%.rdm_atlas", chunk.coord.x, chunk.coord.y, chunk.coord.z);
|
|
||||||
lookup_path = tprint("%_%_%.rdm_lookup", chunk.coord.x, chunk.coord.y, chunk.coord.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
array_add(*chunk_entries, .{
|
array_add(*chunk_entries, .{
|
||||||
coord = chunk.coord,
|
coord = chunk.coord,
|
||||||
offset = running_offset,
|
offset = running_offset,
|
||||||
size = data_size,
|
size = data_size,
|
||||||
rdm_atlas_path = atlas_path,
|
|
||||||
rdm_lookup_path = lookup_path,
|
|
||||||
});
|
});
|
||||||
append(*bin_builder, chunk_data);
|
append(*bin_builder, chunk_data);
|
||||||
running_offset += data_size;
|
running_offset += data_size;
|
||||||
@ -499,8 +506,6 @@ save_world :: (world: *World) -> (json: string, chunks_bin: string) {
|
|||||||
jc.z = entry.coord.z;
|
jc.z = entry.coord.z;
|
||||||
jc.offset = entry.offset;
|
jc.offset = entry.offset;
|
||||||
jc.size = entry.size;
|
jc.size = entry.size;
|
||||||
jc.rdm_atlas = entry.rdm_atlas_path;
|
|
||||||
jc.rdm_lookup = entry.rdm_lookup_path;
|
|
||||||
array_add(*wj.chunks, jc);
|
array_add(*wj.chunks, jc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,7 +525,8 @@ save_world :: (world: *World) -> (json: string, chunks_bin: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ov: world.rdm_overrides {
|
for ov: world.rdm_overrides {
|
||||||
array_add(*wj.rdm_overrides, .{x=ov.x, y=ov.y, z=ov.z, size_override=ov.size_override, quality_override=ov.quality_override});
|
size := ifx ov.rdm_size > 0 then ov.rdm_size else RDM_DEFAULT_SIZE;
|
||||||
|
array_add(*wj.rdm_overrides, .{x=ov.x, y=ov.y, z=ov.z, rdm_enabled=ov.rdm_enabled, rdm_size=size});
|
||||||
}
|
}
|
||||||
|
|
||||||
json_str := Jaison.json_write_string(wj, " ");
|
json_str := Jaison.json_write_string(wj, " ");
|
||||||
@ -542,8 +548,6 @@ load_world_from_json :: (json_str: string, chunk_bin: []u8) -> (World, bool) {
|
|||||||
for jc: wj.chunks {
|
for jc: wj.chunks {
|
||||||
chunk: Chunk;
|
chunk: Chunk;
|
||||||
chunk.coord = .{x = jc.x, y = jc.y, z = jc.z};
|
chunk.coord = .{x = jc.x, y = jc.y, z = jc.z};
|
||||||
chunk.rdm_atlas_path = sprint("%", jc.rdm_atlas);
|
|
||||||
chunk.rdm_lookup_path = sprint("%", jc.rdm_lookup);
|
|
||||||
|
|
||||||
offset := cast(s64) jc.offset;
|
offset := cast(s64) jc.offset;
|
||||||
size := cast(s64) jc.size;
|
size := cast(s64) jc.size;
|
||||||
@ -589,7 +593,8 @@ load_world_from_json :: (json_str: string, chunk_bin: []u8) -> (World, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for jov: wj.rdm_overrides {
|
for jov: wj.rdm_overrides {
|
||||||
array_add(*world.rdm_overrides, .{x=jov.x, y=jov.y, z=jov.z, size_override=jov.size_override, quality_override=jov.quality_override});
|
size := ifx jov.rdm_size > 0 then jov.rdm_size else RDM_DEFAULT_SIZE;
|
||||||
|
array_add(*world.rdm_overrides, .{x=jov.x, y=jov.y, z=jov.z, rdm_enabled=jov.rdm_enabled, rdm_size=size});
|
||||||
}
|
}
|
||||||
|
|
||||||
return world, true;
|
return world, true;
|
||||||
@ -721,7 +726,6 @@ World_Config :: struct {
|
|||||||
waterColor : Vector3 = .{1.0, 1.0, 1.0}; @Color // @ToDo: sensible default values.
|
waterColor : Vector3 = .{1.0, 1.0, 1.0}; @Color // @ToDo: sensible default values.
|
||||||
deepColor : Vector3 = .{1.0, 1.0, 1.0}; @Color // @ToDo: sensible default values.
|
deepColor : Vector3 = .{1.0, 1.0, 1.0}; @Color // @ToDo: sensible default values.
|
||||||
waterShininess : float = 64.0; @Slider,1,512,8
|
waterShininess : float = 64.0; @Slider,1,512,8
|
||||||
rdmDiffSaturation : float = 1.0; @Slider,0,2,0.05
|
|
||||||
hsv_lighting : s32 = 1; @Slider,0,1,1
|
hsv_lighting : s32 = 1; @Slider,0,1,1
|
||||||
|
|
||||||
// ambientColor : Vector3 = .{1.0, 1.0, 1.0}; @Color
|
// ambientColor : Vector3 = .{1.0, 1.0, 1.0}; @Color
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user