merge upstream
@ -9,6 +9,7 @@ if (platform == Platform.OSX) project.addDefine('KORE_DEBUGDIR="osx-hl"');
 | 
			
		||||
if (platform == Platform.iOS) project.addDefine('KORE_DEBUGDIR="ios-hl"');
 | 
			
		||||
if (platform !== Platform.Windows || audio !== AudioApi.DirectSound) {
 | 
			
		||||
	project.addDefine('KORE_MULTITHREADED_AUDIO');
 | 
			
		||||
	project.addDefine('KINC_MULTITHREADED_AUDIO');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
project.addDefine('KORE');
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,55 @@
 | 
			
		||||
package kha.graphics4;
 | 
			
		||||
 | 
			
		||||
using StringTools;
 | 
			
		||||
import kha.Blob;
 | 
			
		||||
 | 
			
		||||
class FragmentShader {
 | 
			
		||||
	public var _shader: Pointer;
 | 
			
		||||
 | 
			
		||||
	public function new(sources: Array<Blob>, files: Array<String>) {
 | 
			
		||||
		initShader(sources[0]);
 | 
			
		||||
		//initShader(sources[0]);
 | 
			
		||||
		var shaderBlob: Blob = null;
 | 
			
		||||
		var shaderFile: String = null;
 | 
			
		||||
 | 
			
		||||
		#if kha_opengl
 | 
			
		||||
		final expectedExtension = ".glsl";
 | 
			
		||||
		#elseif kha_direct3d11
 | 
			
		||||
		final expectedExtension = ".d3d11";
 | 
			
		||||
		#elseif kha_direct3d12
 | 
			
		||||
		final expectedExtension = ".d3d12";
 | 
			
		||||
		#elseif kha_metal
 | 
			
		||||
		final expectedExtension = ".metal";
 | 
			
		||||
		#elseif kha_vulkan
 | 
			
		||||
		final expectedExtension = ".spirv";
 | 
			
		||||
		#else
 | 
			
		||||
		final expectedExtension = ".glsl";
 | 
			
		||||
		#end
 | 
			
		||||
 | 
			
		||||
		if (sources != null && files != null) {
 | 
			
		||||
			for (i in 0...files.length) {
 | 
			
		||||
				if (files[i].endsWith(expectedExtension)) {
 | 
			
		||||
					shaderBlob = sources[i];
 | 
			
		||||
					shaderFile = files[i];
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (shaderBlob == null && sources != null && sources.length > 0) {
 | 
			
		||||
			trace('Warning: Could not find shader with extension ${expectedExtension}. Falling back to sources[0]: ${files != null && files.length > 0 ? files[0] : "Unknown"}');
 | 
			
		||||
			shaderBlob = sources[0];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (shaderBlob != null) {
 | 
			
		||||
			initShader(shaderBlob);
 | 
			
		||||
		} else {
 | 
			
		||||
			trace('Error: No suitable fragment shader source found!');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function initShader(source: Blob): Void {
 | 
			
		||||
		_shader = kinc_create_fragmentshader(source.bytes.getData(), source.bytes.getData().length);
 | 
			
		||||
		//_shader = kinc_create_fragmentshader(source.bytes.getData(), source.bytes.getData().length);
 | 
			
		||||
		_shader = kinc_create_fragmentshader(source.bytes.getData(), source.length); // Use source.length here
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static function fromSource(source: String): FragmentShader {
 | 
			
		||||
 | 
			
		||||
@ -3,36 +3,36 @@
 | 
			
		||||
 | 
			
		||||
#include <hl.h>
 | 
			
		||||
 | 
			
		||||
vbyte *hl_kinc_g4_compute_create_shader(vbyte *data, int length) {
 | 
			
		||||
vbyte *hl_kinc_compute_create_shader(vbyte *data, int length) {
 | 
			
		||||
	kinc_g4_compute_shader *shader = (kinc_g4_compute_shader *)malloc(sizeof(kinc_g4_compute_shader));
 | 
			
		||||
	kinc_g4_compute_shader_init(shader, data, length);
 | 
			
		||||
	return (vbyte *)shader;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void hl_kinc_g4_compute_delete_shader(vbyte *shader) {
 | 
			
		||||
void hl_kinc_compute_delete_shader(vbyte *shader) {
 | 
			
		||||
	kinc_g4_compute_shader *sh = (kinc_g4_compute_shader *)shader;
 | 
			
		||||
	kinc_g4_compute_shader_destroy(sh);
 | 
			
		||||
	free(sh);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
vbyte *hl_kinc_g4_compute_get_constantlocation(vbyte *shader, vbyte *name) {
 | 
			
		||||
vbyte *hl_kinc_compute_get_constantlocation(vbyte *shader, vbyte *name) {
 | 
			
		||||
	kinc_g4_compute_shader *sh = (kinc_g4_compute_shader *)shader;
 | 
			
		||||
	kinc_g4_constant_location_t *location = (kinc_g4_constant_location_t *)malloc(sizeof(kinc_g4_constant_location_t));
 | 
			
		||||
	*location = kinc_g4_compute_shader_get_constant_location(sh, (char *)name), sizeof(kinc_g4_constant_location_t);
 | 
			
		||||
	*location = kinc_g4_compute_shader_get_constant_location(sh, (char *)name);
 | 
			
		||||
	return (vbyte *)location;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
vbyte *hl_kinc_g4_compute_get_textureunit(vbyte *shader, vbyte *name) {
 | 
			
		||||
vbyte *hl_kinc_compute_get_textureunit(vbyte *shader, vbyte *name) {
 | 
			
		||||
	kinc_g4_compute_shader *sh = (kinc_g4_compute_shader *)shader;
 | 
			
		||||
	kinc_g4_texture_unit_t *unit = (kinc_g4_texture_unit_t *)malloc(sizeof(kinc_g4_texture_unit_t));
 | 
			
		||||
	*unit = kinc_g4_compute_shader_get_texture_unit(sh, (char *)name), sizeof(kinc_g4_texture_unit_t);
 | 
			
		||||
	*unit = kinc_g4_compute_shader_get_texture_unit(sh, (char *)name);
 | 
			
		||||
	return (vbyte *)unit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void hl_kinc_g4_set_compute_shader(vbyte *shader) {
 | 
			
		||||
void hl_kinc_set_compute_shader(vbyte *shader) {
 | 
			
		||||
	kinc_g4_set_compute_shader((kinc_g4_compute_shader *)shader);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void hl_kinc_g4_compute(int x, int y, int z) {
 | 
			
		||||
void hl_kinc_compute(int x, int y, int z) {
 | 
			
		||||
	kinc_g4_compute(x, y, z);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -94,8 +94,8 @@ void hl_init_kore(vbyte *title, int width, int height, int samplesPerPixel, bool
 | 
			
		||||
void hl_kinc_init_audio(vclosure *callCallback, vclosure *readSample, int *outSamplesPerSecond) {
 | 
			
		||||
	audioCallCallback = *((FN_AUDIO_CALL_CALLBACK *)(&callCallback->fun));
 | 
			
		||||
	audioReadSample = *((FN_AUDIO_READ_SAMPLE *)(&readSample->fun));
 | 
			
		||||
	kinc_a2_set_callback(mix, NULL);
 | 
			
		||||
	kinc_a2_init();
 | 
			
		||||
	kinc_a2_set_callback(mix, NULL);
 | 
			
		||||
	*outSamplesPerSecond = kinc_a2_samples_per_second();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB  | 
| 
		 Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB  | 
| 
		 Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB  | 
| 
		 Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB  | 
| 
		 Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB  | 
| 
		 Before Width: | Height: | Size: 417 KiB After Width: | Height: | Size: 281 KiB  | 
@ -160,6 +160,7 @@ class LnxPack {
 | 
			
		||||
			case "anim": TAnimation;
 | 
			
		||||
			case "tracks": TTrack;
 | 
			
		||||
			case "morph_target": TMorphTarget;
 | 
			
		||||
			case "vertex_groups": TVertex_groups;
 | 
			
		||||
			case _: TSceneFormat;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -427,6 +427,19 @@ def build(target, is_play=False, is_publish=False, is_export=False):
 | 
			
		||||
    global profile_time
 | 
			
		||||
    profile_time = time.time()
 | 
			
		||||
 | 
			
		||||
    wrd = bpy.data.worlds['Lnx']
 | 
			
		||||
 | 
			
		||||
    if is_play and wrd.lnx_runtime == 'Hashlink':
 | 
			
		||||
        current_os = lnx.utils.get_os()
 | 
			
		||||
        if current_os == 'win':
 | 
			
		||||
            target = 'windows-hl'
 | 
			
		||||
        elif current_os == 'linux':
 | 
			
		||||
            target = 'linux-hl'
 | 
			
		||||
        elif current_os == 'macos':
 | 
			
		||||
            target = 'macos-hl'
 | 
			
		||||
        else:
 | 
			
		||||
            log.error(f"Unsupported OS '{current_os}' for Hashlink runtime.")
 | 
			
		||||
 | 
			
		||||
    state.target = target
 | 
			
		||||
    state.is_play = is_play
 | 
			
		||||
    state.is_publish = is_publish
 | 
			
		||||
@ -680,6 +693,148 @@ def build_success():
 | 
			
		||||
                    cmd.append(str(pid))
 | 
			
		||||
                if wrd.lnx_audio == 'Disabled':
 | 
			
		||||
                    cmd.append('--nosound')
 | 
			
		||||
                    
 | 
			
		||||
        elif state.target.startswith(('windows-hl', 'linux-hl', 'macos-hl')):
 | 
			
		||||
            log.info(f"Runtime Hashlink/C target: {state.target}")
 | 
			
		||||
 | 
			
		||||
            hl_build_dir, _, _ = lnx.utils.hashlink_paths(state.target)
 | 
			
		||||
 | 
			
		||||
            if not hl_build_dir:
 | 
			
		||||
                 log.error(f"Could not find build directory for target {state.target}. Playback aborted.")
 | 
			
		||||
                 return 
 | 
			
		||||
 | 
			
		||||
            if state.target == 'windows-hl':
 | 
			
		||||
                vs_version_major = wrd.lnx_project_win_list_vs
 | 
			
		||||
                build_mode = wrd.lnx_project_win_build_mode # Debug or Release
 | 
			
		||||
                build_arch = wrd.lnx_project_win_build_arch # x64 or x86 (maps to Win32 for MSBuild)
 | 
			
		||||
                platform = 'x64' if build_arch == 'x64' else 'Win32' # MSBuild uses Win32 for x86
 | 
			
		||||
 | 
			
		||||
                installation = lnx.utils_vs.get_installed_version(vs_version_major, re_fetch=True)
 | 
			
		||||
                if installation is None:
 | 
			
		||||
                    vs_info = lnx.utils_vs.get_supported_version(vs_version_major)
 | 
			
		||||
                    log.error(f'Visual Studio {vs_info["name"]} not found. Cannot compile {state.target}.')
 | 
			
		||||
                    return
 | 
			
		||||
                msbuild_path = os.path.join(installation['path'], 'MSBuild', 'Current', 'Bin', 'MSBuild.exe')
 | 
			
		||||
                if not os.path.isfile(msbuild_path):
 | 
			
		||||
                    msbuild_path = os.path.join(installation['path'], 'MSBuild', '15.0', 'Bin', 'MSBuild.exe') # VS 2017 fallback
 | 
			
		||||
                    if not os.path.isfile(msbuild_path):
 | 
			
		||||
                         log.error(f'MSBuild.exe not found for {installation["name"]}. Cannot compile {state.target}.')
 | 
			
		||||
                         return
 | 
			
		||||
 | 
			
		||||
                proj_name = lnx.utils.blend_name()
 | 
			
		||||
                vcxproj_path = os.path.join(hl_build_dir, proj_name + '.vcxproj')
 | 
			
		||||
                if not os.path.isfile(vcxproj_path):
 | 
			
		||||
                    found_vcxproj = None
 | 
			
		||||
                    for file in os.listdir(hl_build_dir):
 | 
			
		||||
                        if file.endswith(".vcxproj"):
 | 
			
		||||
                            found_vcxproj = os.path.join(hl_build_dir, file)
 | 
			
		||||
                            log.warn(f"Could not find '{proj_name}.vcxproj', using found '{file}' instead.")
 | 
			
		||||
                            break
 | 
			
		||||
                    if not found_vcxproj:
 | 
			
		||||
                        log.error(f'.vcxproj file not found in {hl_build_dir}. Cannot compile.')
 | 
			
		||||
                        return
 | 
			
		||||
                    vcxproj_path = found_vcxproj
 | 
			
		||||
                    proj_name = os.path.splitext(os.path.basename(vcxproj_path))[0] 
 | 
			
		||||
 | 
			
		||||
                msbuild_cmd = [
 | 
			
		||||
                    msbuild_path,
 | 
			
		||||
                    vcxproj_path,
 | 
			
		||||
                    f'/p:Configuration={build_mode}',
 | 
			
		||||
                    f'/p:Platform={platform}',
 | 
			
		||||
                    '/m' 
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
                log.info(f"Compiling {state.target} project with MSBuild...")
 | 
			
		||||
                log.info(f"Command: {' '.join(msbuild_cmd)}")
 | 
			
		||||
                compile_success = False
 | 
			
		||||
                try:
 | 
			
		||||
                    compile_result = subprocess.run(msbuild_cmd, cwd=hl_build_dir, check=False, capture_output=True, text=True)
 | 
			
		||||
                    if compile_result.returncode == 0:
 | 
			
		||||
                        log.info(f"MSBuild compilation successful.")
 | 
			
		||||
                        compile_success = True
 | 
			
		||||
                    else:
 | 
			
		||||
                        log.error(f"MSBuild compilation failed (Exit Code: {compile_result.returncode}).")
 | 
			
		||||
                        log.error(f"MSBuild Output:\n{compile_result.stdout}")
 | 
			
		||||
                        log.error(f"MSBuild Errors:\n{compile_result.stderr}")
 | 
			
		||||
                except Exception as e:
 | 
			
		||||
                    log.error(f"Error running MSBuild: {e}")
 | 
			
		||||
                    traceback.print_exc()
 | 
			
		||||
 | 
			
		||||
                if not compile_success:
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
                exe_path = os.path.join(hl_build_dir, platform, build_mode, proj_name + '.exe')
 | 
			
		||||
                if not os.path.isfile(exe_path):
 | 
			
		||||
                    exe_path = os.path.join(hl_build_dir, build_mode, proj_name + '.exe')
 | 
			
		||||
                    if not os.path.isfile(exe_path):
 | 
			
		||||
                        log.error(f'Compiled executable not found at expected location: {exe_path} (or variants). Cannot run.')
 | 
			
		||||
                        return
 | 
			
		||||
 | 
			
		||||
                log.info(f"Found compiled executable: {exe_path}")
 | 
			
		||||
                
 | 
			
		||||
                dest_exe_name = proj_name + '.exe'
 | 
			
		||||
                base_build_dir = lnx.utils.get_fp_build()
 | 
			
		||||
                dest_dir = os.path.join(base_build_dir, state.target)
 | 
			
		||||
                dest_path = os.path.join(dest_dir, dest_exe_name)
 | 
			
		||||
                try:
 | 
			
		||||
                    shutil.move(exe_path, dest_path)
 | 
			
		||||
                    cmd = [dest_path]
 | 
			
		||||
                except Exception as e:
 | 
			
		||||
                    cmd = [exe_path]
 | 
			
		||||
                os.chdir(dest_dir)
 | 
			
		||||
 | 
			
		||||
            elif state.target in ('linux-hl', 'macos-hl'):
 | 
			
		||||
                wrd = bpy.data.worlds['Lnx']
 | 
			
		||||
                paths = lnx.utils.hashlink_paths(state.target)
 | 
			
		||||
                hl_build_dir = paths[0]
 | 
			
		||||
                # TO DO switch from default Release
 | 
			
		||||
                build_mode = 'Release'
 | 
			
		||||
                proj_name = lnx.utils.blend_name()
 | 
			
		||||
                exe_path = str(hl_build_dir + "/" + build_mode) 
 | 
			
		||||
                if not exe_path:
 | 
			
		||||
                    log.error(f"Build finished, but could not find the executable for {state.target}.")
 | 
			
		||||
                    return
 | 
			
		||||
                makefile_path = os.path.join(exe_path, 'makefile')
 | 
			
		||||
                if not os.path.isfile(makefile_path):
 | 
			
		||||
                    log.error(f"Makefile not found at '{makefile_path}'. Cannot compile C code.")
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
                make_cmd = ['make']
 | 
			
		||||
 | 
			
		||||
                log.info(f"Compiling C code using 'make' in directory '{exe_path}'...")
 | 
			
		||||
                log.info(f"Make command: {' '.join(make_cmd)}")
 | 
			
		||||
                try:
 | 
			
		||||
                    result = subprocess.run(make_cmd, cwd=exe_path, check=True, capture_output=True, text=True, encoding='utf-8')
 | 
			
		||||
                    log.info("'make' compilation successful.")
 | 
			
		||||
                except subprocess.CalledProcessError as e:
 | 
			
		||||
                    log.error(f"'make' compilation failed with return code {e.returncode}.")
 | 
			
		||||
                    log.error(f"Make Error Output:\n{e.stderr}")
 | 
			
		||||
                    return 
 | 
			
		||||
                except FileNotFoundError:
 | 
			
		||||
                    log.error("'make' command not found. Ensure 'make' is installed and in your system's PATH.")
 | 
			
		||||
                    return
 | 
			
		||||
                except Exception as e:
 | 
			
		||||
                    log.error(f"An unexpected error occurred running make: {e}")
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
                log.info(f"Found compiled executable: {exe_path}")
 | 
			
		||||
 | 
			
		||||
                dest_exe_name = lnx.utils.safesrc(wrd.lnx_project_name + '-' + wrd.lnx_project_version)
 | 
			
		||||
                base_build_dir = lnx.utils.get_fp_build()
 | 
			
		||||
                dest_dir = os.path.join(base_build_dir, state.target) 
 | 
			
		||||
                og_path = os.path.join(exe_path, dest_exe_name)
 | 
			
		||||
                dest_path = os.path.join(dest_dir, dest_exe_name)
 | 
			
		||||
                os.makedirs(dest_dir, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    log.info(f"Moving '{og_path}' to '{dest_dir}'...")
 | 
			
		||||
                    shutil.move(og_path, dest_dir) 
 | 
			
		||||
                    cmd = [dest_path] 
 | 
			
		||||
                except Exception as e:
 | 
			
		||||
                    log.error(f"Failed to move executable: {e}. Attempting to run from original location.")
 | 
			
		||||
                    cmd = [exe_path]
 | 
			
		||||
                os.chdir(dest_dir)
 | 
			
		||||
                log.info(f"Hashlink final command: {' '.join(cmd)}")
 | 
			
		||||
        try:
 | 
			
		||||
            state.proc_play = run_proc(cmd, play_done)
 | 
			
		||||
        except Exception:
 | 
			
		||||
 | 
			
		||||
@ -308,7 +308,8 @@ def init_properties():
 | 
			
		||||
    bpy.types.World.lnx_verbose_output = BoolProperty(name="Verbose Output", description="Print additional information to the console during compilation", default=False)
 | 
			
		||||
    bpy.types.World.lnx_runtime = EnumProperty(
 | 
			
		||||
        items=[('Krom', 'Krom', 'Krom'),
 | 
			
		||||
               ('Browser', 'Browser', 'Browser')],
 | 
			
		||||
               ('Browser', 'Browser', 'Browser'),
 | 
			
		||||
               ('Hashlink', 'Hashlink', 'Hashlink')],
 | 
			
		||||
        name="Runtime", description="Runtime to use when launching the game", default='Krom', update=assets.invalidate_shader_cache)
 | 
			
		||||
    bpy.types.World.lnx_loadscreen = BoolProperty(name="Loading Screen", description="Show asset loading progress on published builds", default=True)
 | 
			
		||||
    bpy.types.World.lnx_vsync = BoolProperty(name="VSync", description="Vertical Synchronization", default=True, update=assets.invalidate_compiler_cache)
 | 
			
		||||
 | 
			
		||||
@ -363,6 +363,25 @@ def krom_paths():
 | 
			
		||||
        krom_path = krom_location + '/Krom'
 | 
			
		||||
    return krom_location, krom_path
 | 
			
		||||
 | 
			
		||||
def hashlink_paths(target):
 | 
			
		||||
    """Returns path for Hashlink runtime target."""
 | 
			
		||||
    sdk_path = get_sdk_path()
 | 
			
		||||
    proj_name = blend_name()
 | 
			
		||||
    build_base_dir = get_fp_build()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if target in ('windows-hl', 'linux-hl', 'macos-hl'): 
 | 
			
		||||
        hl_build_dir = os.path.join(build_base_dir, target + '-build')
 | 
			
		||||
        log.info(f"Identified Hashlink/C build directory: {hl_build_dir}")
 | 
			
		||||
 | 
			
		||||
        return hl_build_dir, None, None # Return build_dir, no file, no interpreter
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        return '', None, None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fetch_bundled_script_names():
 | 
			
		||||
    wrd = bpy.data.worlds['Lnx']
 | 
			
		||||
    wrd.lnx_bundled_scripts_list.clear()
 | 
			
		||||
 | 
			
		||||
@ -64,6 +64,11 @@ class Aura {
 | 
			
		||||
 | 
			
		||||
		BufferCache.init();
 | 
			
		||||
		
 | 
			
		||||
		// Sample buffer to prevent allocation 
 | 
			
		||||
		final initialBufferSize = 4096; 
 | 
			
		||||
		if (!BufferCache.getBuffer(TFloat32Array, p_samplesBuffer, 1, initialBufferSize)) {
 | 
			
		||||
			trace('CRITICAL: Failed to allocate initial sample buffer during Aura.init()!');
 | 
			
		||||
		}
 | 
			
		||||
		// Create a few preconfigured mix channels
 | 
			
		||||
		masterChannel = createMixChannel("master");
 | 
			
		||||
		createMixChannel("music").setMixChannel(masterChannel);
 | 
			
		||||
 | 
			
		||||