forked from LeenkxTeam/LNXSDK
Patch_5
This commit is contained in:
@ -114,6 +114,7 @@ extern class Krom {
|
||||
static function windowWidth(id: Int): Int;
|
||||
static function windowHeight(id: Int): Int;
|
||||
static function setWindowTitle(id: Int, title: String): Void;
|
||||
static function windowSetForeground(id: Int): Void;
|
||||
static function screenDpi(): Int;
|
||||
static function systemId(): String;
|
||||
static function requestShutdown(): Void;
|
||||
|
||||
BIN
Krom/Krom.exe
BIN
Krom/Krom.exe
Binary file not shown.
Binary file not shown.
@ -1096,17 +1096,17 @@ class Inc {
|
||||
g.setImageTexture(voxel_tb2, rts.get(write_sdf).image);
|
||||
|
||||
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
|
||||
for (i in 0...Main.voxelgiClipmapCount) {
|
||||
fa[i * 10] = clipmaps[i].voxelSize;
|
||||
fa[i * 10 + 1] = clipmaps[i].extents.x;
|
||||
fa[i * 10 + 2] = clipmaps[i].extents.y;
|
||||
fa[i * 10 + 3] = clipmaps[i].extents.z;
|
||||
fa[i * 10 + 4] = clipmaps[i].center.x;
|
||||
fa[i * 10 + 5] = clipmaps[i].center.y;
|
||||
fa[i * 10 + 6] = clipmaps[i].center.z;
|
||||
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
|
||||
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
|
||||
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
|
||||
for (j in 0...Main.voxelgiClipmapCount) {
|
||||
fa[j * 10] = clipmaps[j].voxelSize;
|
||||
fa[j * 10 + 1] = clipmaps[j].extents.x;
|
||||
fa[j * 10 + 2] = clipmaps[j].extents.y;
|
||||
fa[j * 10 + 3] = clipmaps[j].extents.z;
|
||||
fa[j * 10 + 4] = clipmaps[j].center.x;
|
||||
fa[j * 10 + 5] = clipmaps[j].center.y;
|
||||
fa[j * 10 + 6] = clipmaps[j].center.z;
|
||||
fa[j * 10 + 7] = clipmaps[j].offset_prev.x;
|
||||
fa[j * 10 + 8] = clipmaps[j].offset_prev.y;
|
||||
fa[j * 10 + 9] = clipmaps[j].offset_prev.z;
|
||||
}
|
||||
|
||||
g.setFloats(voxel_ca2, fa);
|
||||
@ -1407,17 +1407,17 @@ class Inc {
|
||||
g.setTexture(voxel_tg5, rts.get("voxelsSDF").image);
|
||||
|
||||
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
|
||||
for (i in 0...Main.voxelgiClipmapCount) {
|
||||
fa[i * 10] = clipmaps[i].voxelSize;
|
||||
fa[i * 10 + 1] = clipmaps[i].extents.x;
|
||||
fa[i * 10 + 2] = clipmaps[i].extents.y;
|
||||
fa[i * 10 + 3] = clipmaps[i].extents.z;
|
||||
fa[i * 10 + 4] = clipmaps[i].center.x;
|
||||
fa[i * 10 + 5] = clipmaps[i].center.y;
|
||||
fa[i * 10 + 6] = clipmaps[i].center.z;
|
||||
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
|
||||
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
|
||||
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
|
||||
for (j in 0...Main.voxelgiClipmapCount) {
|
||||
fa[j * 10] = clipmaps[j].voxelSize;
|
||||
fa[j * 10 + 1] = clipmaps[j].extents.x;
|
||||
fa[j * 10 + 2] = clipmaps[j].extents.y;
|
||||
fa[j * 10 + 3] = clipmaps[j].extents.z;
|
||||
fa[j * 10 + 4] = clipmaps[j].center.x;
|
||||
fa[j * 10 + 5] = clipmaps[j].center.y;
|
||||
fa[j * 10 + 6] = clipmaps[j].center.z;
|
||||
fa[j * 10 + 7] = clipmaps[j].offset_prev.x;
|
||||
fa[j * 10 + 8] = clipmaps[j].offset_prev.y;
|
||||
fa[j * 10 + 9] = clipmaps[j].offset_prev.z;
|
||||
}
|
||||
|
||||
g.setFloats(voxel_ca5, fa);
|
||||
|
||||
@ -415,9 +415,8 @@ class RenderPathDeferred {
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = Inc.getHdrFormat();
|
||||
t.format = "RGBA64";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
t.depth_buffer = "main";
|
||||
path.createRenderTarget(t);
|
||||
}
|
||||
#end
|
||||
@ -869,6 +868,7 @@ class RenderPathDeferred {
|
||||
|
||||
#if rp_water
|
||||
{
|
||||
path.setDepthFrom("tex", "gbuffer1");
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
@ -876,6 +876,7 @@ class RenderPathDeferred {
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/water_pass/water_pass");
|
||||
path.setDepthFrom("tex", "gbuffer0");
|
||||
}
|
||||
#end
|
||||
|
||||
@ -951,20 +952,34 @@ class RenderPathDeferred {
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
||||
{
|
||||
//#if (!kha_opengl)
|
||||
//path.setDepthFrom("gbuffer0", "gbuffer1"); // Unbind depth so we can read it
|
||||
//path.depthToRenderTarget.set("main", path.renderTargets.get("tex"));
|
||||
//#end
|
||||
|
||||
//save depth
|
||||
path.setTarget("gbufferD1");
|
||||
path.bindTarget("_main", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
|
||||
//#if (!kha_opengl)
|
||||
//path.setDepthFrom("gbuffer0", "tex"); // Re-bind depth
|
||||
//path.depthToRenderTarget.set("main", path.renderTargets.get("gbuffer0"));
|
||||
//#end
|
||||
|
||||
//save background color
|
||||
path.setTarget("refr");
|
||||
path.bindTarget("tex", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
|
||||
path.setTarget("gbuffer0", ["tex", "gbuffer_refraction",
|
||||
#if rp_gbuffer2 "gbuffer1", #end
|
||||
#if rp_gbuffer_emission "buf", #end
|
||||
]);
|
||||
path.setTarget("gbuffer0", ["gbuffer1", "gbuffer_refraction"]);
|
||||
|
||||
#if (rp_voxels != "Off")
|
||||
path.bindTarget("voxelsOut", "voxels");
|
||||
#if (rp_voxels == "Voxel GI" || lnx_voxelgi_shadows)
|
||||
path.bindTarget("voxelsSDF", "voxelsSDF");
|
||||
#end
|
||||
#end
|
||||
|
||||
#if rp_shadowmap
|
||||
{
|
||||
@ -976,32 +991,21 @@ class RenderPathDeferred {
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_voxels != "Off")
|
||||
path.bindTarget("voxelsOut", "voxels");
|
||||
#if (rp_voxels == "Voxel GI" || lnx_voxelgi_shadows)
|
||||
path.bindTarget("voxelsSDF", "voxelsSDF");
|
||||
#end
|
||||
#end
|
||||
|
||||
#if rp_ssrs
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
#end
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("tex", "tex"); // scene with refractive objects
|
||||
path.bindTarget("refr", "tex1"); // background without refractive objects
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("refr", "tex");
|
||||
path.bindTarget("gbufferD1", "gbufferD1");
|
||||
path.bindTarget("gbuffer0", "gbuffer0");
|
||||
path.bindTarget("gbuffer1", "tex1");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
|
||||
path.setTarget("tex");
|
||||
path.bindTarget("buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -1168,8 +1172,10 @@ class RenderPathDeferred {
|
||||
#else
|
||||
path.isProbe ? path.setTarget(framebuffer) : path.setTarget("bufa");
|
||||
#end
|
||||
#elseif (rp_supersampling == 4)
|
||||
path.setTarget("bufa");
|
||||
#elseif rp_fsr1
|
||||
path.setTarget("buf");
|
||||
path.setTarget("bufa");
|
||||
#else
|
||||
path.setTarget(framebuffer);
|
||||
#end
|
||||
@ -1182,21 +1188,36 @@ class RenderPathDeferred {
|
||||
#end
|
||||
path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend");
|
||||
|
||||
#if ((rp_supersampling == 4) || rp_fsr1)
|
||||
#if (rp_antialiasing == "SMAA")
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (rp_antialiasing == "TAA")
|
||||
{
|
||||
if (!path.isProbe) { // No last frame for probe
|
||||
path.setTarget("taa");
|
||||
// Write TAA blend to bufb to avoid read-write hazard on taa
|
||||
path.setTarget("bufb");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.bindTarget("taa", "tex2");
|
||||
path.bindTarget("gbuffer2", "sveloc");
|
||||
path.drawShader("shader_datas/taa_pass/taa_pass");
|
||||
|
||||
// Save blended result as history for next frame
|
||||
path.setTarget("taa");
|
||||
path.bindTarget("bufb", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
|
||||
// Output to framebuffer
|
||||
#if rp_fsr1
|
||||
path.setTarget("buf");
|
||||
#else
|
||||
path.setTarget(framebuffer);
|
||||
#end
|
||||
path.bindTarget("taa", "tex");
|
||||
path.bindTarget("bufb", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
@ -1220,7 +1241,7 @@ class RenderPathDeferred {
|
||||
var finalTarget = "";
|
||||
path.setTarget(finalTarget);
|
||||
path.bindTarget(framebuffer, "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
path.drawShader("shader_datas/supersample_resolve/supersample_resolve");
|
||||
}
|
||||
#end
|
||||
|
||||
|
||||
@ -147,7 +147,7 @@ class RenderPathForward {
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.displayp = Inc.getDisplayp();
|
||||
t.format = "DEPTH24";
|
||||
t.format = "R32";
|
||||
t.scale = Inc.getSuperSampling();
|
||||
path.createRenderTarget(t);
|
||||
|
||||
@ -446,6 +446,18 @@ class RenderPathForward {
|
||||
|
||||
path.bindTarget("voxels", "voxels");
|
||||
|
||||
#if rp_shadowmap
|
||||
{
|
||||
#if lnx_shadowmap_atlas
|
||||
Inc.bindShadowMapAtlas();
|
||||
#else
|
||||
Inc.bindShadowMap();
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
path.drawMeshes("voxel");
|
||||
|
||||
Inc.computeVoxelsTemporal();
|
||||
|
||||
#if (rp_voxels == "Voxel GI")
|
||||
@ -473,7 +485,7 @@ class RenderPathForward {
|
||||
#if (rp_ssrefr || lnx_voxelgi_refract)
|
||||
{
|
||||
path.setTarget("gbuffer_refraction");
|
||||
path.clearTarget(0xffff00ff);
|
||||
path.clearTarget(0xffffff00);
|
||||
}
|
||||
#end
|
||||
|
||||
@ -544,11 +556,21 @@ class RenderPathForward {
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
||||
{
|
||||
#if (!kha_opengl)
|
||||
path.setDepthFrom("lbuffer0", "bufa"); // Unbind depth so we can read it
|
||||
path.depthToRenderTarget.set("main", path.renderTargets.get("buf"));
|
||||
#end
|
||||
|
||||
//save depth
|
||||
path.setTarget("gbufferD1");
|
||||
path.bindTarget("_main", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
|
||||
#if (!kha_opengl)
|
||||
path.setDepthFrom("lbuffer0", "buf"); // Re-bind depth
|
||||
path.depthToRenderTarget.set("main", path.renderTargets.get("lbuffer0"));
|
||||
#end
|
||||
|
||||
//save background color
|
||||
path.setTarget("refr");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
@ -579,18 +601,17 @@ class RenderPathForward {
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("bufa");
|
||||
path.setTarget("lbuffer0");
|
||||
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.bindTarget("refr", "tex1");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbufferD1", "gbufferD1");
|
||||
path.bindTarget("lbuffer1", "gbuffer0");
|
||||
path.bindTarget("lbuffer0", "gbuffer1");
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
@ -630,53 +651,6 @@ class RenderPathForward {
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_ssrefr
|
||||
{
|
||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
||||
{
|
||||
path.setTarget("gbufferD1");
|
||||
path.bindTarget("_main", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
|
||||
path.setTarget("refr");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
|
||||
path.setTarget("lbuffer0", ["lbuffer1", "gbuffer_refraction"]);
|
||||
|
||||
#if rp_shadowmap
|
||||
{
|
||||
#if lnx_shadowmap_atlas
|
||||
Inc.bindShadowMapAtlas();
|
||||
#else
|
||||
Inc.bindShadowMap();
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
#if (rp_voxels != "Off")
|
||||
path.bindTarget("voxelsOut", "voxels");
|
||||
path.bindTarget("voxelsSDF", "voxelsSDF");
|
||||
#end
|
||||
|
||||
path.drawMeshes("refraction");
|
||||
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
path.bindTarget("refr", "tex1");
|
||||
path.bindTarget("_main", "gbufferD");
|
||||
path.bindTarget("gbufferD1", "gbufferD1");
|
||||
path.bindTarget("lbuffer1", "gbuffer0");
|
||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||
|
||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||
path.setTarget("lbuffer0");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
#if rp_bloom
|
||||
{
|
||||
inline Inc.drawBloom("lbuffer0", bloomDownsampler, bloomUpsampler);
|
||||
@ -718,8 +692,10 @@ class RenderPathForward {
|
||||
|
||||
#if rp_water
|
||||
{
|
||||
#if (!kha_opengl)
|
||||
path.setDepthFrom("lbuffer0", "bufa"); // Unbind depth so we can read it
|
||||
path.depthToRenderTarget.set("main", path.renderTargets.get("buf"));
|
||||
#end
|
||||
|
||||
path.setTarget("bufa");
|
||||
path.bindTarget("lbuffer0", "tex");
|
||||
@ -730,8 +706,10 @@ class RenderPathForward {
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/water_pass/water_pass");
|
||||
|
||||
#if (!kha_opengl)
|
||||
path.setDepthFrom("lbuffer0", "buf"); // Re-bind depth
|
||||
path.depthToRenderTarget.set("main", path.renderTargets.get("lbuffer0"));
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
@ -794,10 +772,20 @@ class RenderPathForward {
|
||||
path.bindTarget("bufa", "edgesTex");
|
||||
path.drawShader("shader_datas/smaa_blend_weight/smaa_blend_weight");
|
||||
|
||||
#if (rp_supersampling == 4)
|
||||
path.setTarget("bufa");
|
||||
#else
|
||||
path.setTarget(framebuffer);
|
||||
#end
|
||||
path.bindTarget("buf", "colorTex");
|
||||
path.bindTarget("bufb", "blendTex");
|
||||
path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend");
|
||||
|
||||
#if (rp_supersampling == 4)
|
||||
path.setTarget("buf");
|
||||
path.bindTarget("bufa", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
#end
|
||||
}
|
||||
#end
|
||||
|
||||
@ -805,17 +793,16 @@ class RenderPathForward {
|
||||
{
|
||||
// FSR1 RCAS sharpening pass applied after AA, expects sRGB [0-1] input
|
||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
// SMAA outputs to framebuffer which needs an intermediate buffer
|
||||
path.setTarget("bufb");
|
||||
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufb";
|
||||
var fsrDest = "";
|
||||
#end
|
||||
// SMAA outputs to framebuffer which needs an intermediate buffer
|
||||
path.setTarget("bufb");
|
||||
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
|
||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||
var fsrSource = "bufb";
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrDest = "buf";
|
||||
#else
|
||||
var fsrDest = "";
|
||||
#end
|
||||
#else
|
||||
#if (rp_supersampling == 4)
|
||||
var fsrSource = "buf";
|
||||
|
||||
@ -2198,25 +2198,28 @@ class LeenkxExporter:
|
||||
light_object = light_objects[0] if len(light_objects) > 0 else None
|
||||
objtype = light_ref.type
|
||||
color = [light_ref.color[0], light_ref.color[1], light_ref.color[2]]
|
||||
if light_ref.use_temperature:
|
||||
temperature_color = light_ref.temperature_color
|
||||
color[0] *= temperature_color[0]
|
||||
color[1] *= temperature_color[1]
|
||||
color[2] *= temperature_color[2]
|
||||
if bpy.app.version >= (4, 5, 0):
|
||||
if light_ref.use_temperature:
|
||||
temperature_color = light_ref.temperature_color
|
||||
color[0] *= temperature_color[0]
|
||||
color[1] *= temperature_color[1]
|
||||
color[2] *= temperature_color[2]
|
||||
|
||||
strength = light_ref.energy * math.pow(2.0, light_ref.exposure)
|
||||
if not light_ref.normalize:
|
||||
area = 0.0
|
||||
try:
|
||||
if light_object is not None:
|
||||
area = light_ref.area(matrix_world=light_object.matrix_world)
|
||||
else:
|
||||
strength = light_ref.energy * math.pow(2.0, light_ref.exposure)
|
||||
if not light_ref.normalize:
|
||||
area = 0.0
|
||||
try:
|
||||
if light_object is not None:
|
||||
area = light_ref.area(matrix_world=light_object.matrix_world)
|
||||
else:
|
||||
area = light_ref.area()
|
||||
except TypeError:
|
||||
area = light_ref.area()
|
||||
except TypeError:
|
||||
area = light_ref.area()
|
||||
|
||||
if area > 0.0:
|
||||
strength *= area
|
||||
if area > 0.0:
|
||||
strength *= area
|
||||
else:
|
||||
strength = light_ref.energy
|
||||
|
||||
out_light = {
|
||||
'name': object_ref[1]["structName"],
|
||||
|
||||
@ -3,7 +3,6 @@ import os
|
||||
import queue
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import types
|
||||
from typing import Dict, Tuple, Callable, Set
|
||||
|
||||
@ -13,6 +12,7 @@ from bpy.app.handlers import persistent
|
||||
import lnx
|
||||
import lnx.api
|
||||
import lnx.nodes_logic
|
||||
import lnx.render_engine
|
||||
import lnx.make_state as state
|
||||
import lnx.utils
|
||||
import lnx.utils_vs
|
||||
@ -25,6 +25,7 @@ if lnx.is_reload(__name__):
|
||||
log = lnx.reload_module(log)
|
||||
lnx_nodes = lnx.reload_module(lnx_nodes)
|
||||
lnx.nodes_logic = lnx.reload_module(lnx.nodes_logic)
|
||||
lnx.render_engine = lnx.reload_module(lnx.render_engine)
|
||||
make = lnx.reload_module(make)
|
||||
state = lnx.reload_module(state)
|
||||
props = lnx.reload_module(props)
|
||||
@ -33,10 +34,8 @@ if lnx.is_reload(__name__):
|
||||
else:
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
# Module-level storage for active threads (eliminates re-queuing overhead)
|
||||
# Module-level storage for active threads
|
||||
_active_threads: Dict[threading.Thread, Callable] = {}
|
||||
_last_poll_time = 0.0
|
||||
_consecutive_empty_polls = 0
|
||||
_last_render_engine = None
|
||||
|
||||
@persistent
|
||||
@ -169,6 +168,14 @@ def check_render_engine() -> float:
|
||||
|
||||
elif _last_render_engine == 'KROM_VIEWPORT':
|
||||
try:
|
||||
for vid, engine in list(lnx.render_engine._active_krom_engines.items()):
|
||||
try:
|
||||
engine._restore_overlay()
|
||||
except:
|
||||
pass
|
||||
lnx.render_engine._active_krom_engines.clear()
|
||||
lnx.render_engine._active_krom_engine = None
|
||||
lnx.render_engine._active_viewport_id = None
|
||||
make.stop_viewport()
|
||||
except Exception as e:
|
||||
log.warn(f'Failed to stop viewport: {e}')
|
||||
@ -183,113 +190,31 @@ def check_render_engine() -> float:
|
||||
|
||||
|
||||
def poll_threads() -> float:
|
||||
"""
|
||||
Improved thread polling with:
|
||||
- No re-queuing overhead
|
||||
- Batch processing of completed threads
|
||||
- Adaptive timing based on activity
|
||||
- Better memory management
|
||||
- Simplified logic flow
|
||||
"""
|
||||
global _last_poll_time, _consecutive_empty_polls
|
||||
current_time = time.time()
|
||||
|
||||
# Process all new threads from queue at once (batch processing)
|
||||
new_threads_added = 0
|
||||
"""Polls the thread callback queue and processes completed threads."""
|
||||
# Drain queue into active threads
|
||||
try:
|
||||
while True:
|
||||
thread, callback = make.thread_callback_queue.get(block=False)
|
||||
_active_threads[thread] = callback
|
||||
new_threads_added += 1
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
# Early return if no active threads
|
||||
|
||||
if not _active_threads:
|
||||
_consecutive_empty_polls += 1
|
||||
# Adaptive timing: longer intervals when consistently empty
|
||||
if _consecutive_empty_polls > 10:
|
||||
return 0.5 # Back off when no activity
|
||||
return 0.25
|
||||
|
||||
# Reset empty poll counter when we have active threads
|
||||
_consecutive_empty_polls = 0
|
||||
|
||||
# Find completed threads (single pass, no re-queuing)
|
||||
completed_threads = []
|
||||
|
||||
# Join and callback all completed threads
|
||||
for thread in list(_active_threads.keys()):
|
||||
if not thread.is_alive():
|
||||
completed_threads.append(thread)
|
||||
|
||||
# Batch process all completed threads
|
||||
if completed_threads:
|
||||
_process_completed_threads(completed_threads)
|
||||
|
||||
# Adaptive timing based on activity level
|
||||
active_count = len(_active_threads)
|
||||
if active_count == 0:
|
||||
return 0.25
|
||||
elif active_count <= 3:
|
||||
return 0.05 # Medium frequency for low activity
|
||||
else:
|
||||
return 0.01 # High frequency for high activity
|
||||
callback = _active_threads.pop(thread)
|
||||
try:
|
||||
thread.join()
|
||||
callback()
|
||||
except Exception as e:
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
|
||||
raise e
|
||||
|
||||
def _process_completed_threads(completed_threads: list) -> None:
|
||||
"""Process a batch of completed threads with robust error handling."""
|
||||
for thread in completed_threads:
|
||||
callback = _active_threads.pop(thread) # Remove from tracking
|
||||
|
||||
try:
|
||||
thread.join() # Should be instant since thread is dead
|
||||
callback()
|
||||
except Exception as e:
|
||||
# Robust error recovery
|
||||
_handle_callback_error(e)
|
||||
continue # Continue processing other threads
|
||||
|
||||
# Explicit cleanup for better memory management
|
||||
del thread, callback
|
||||
|
||||
def _handle_callback_error(exception: Exception) -> None:
|
||||
"""Centralized error handling with better recovery."""
|
||||
try:
|
||||
# Try to unregister existing timer
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
except ValueError:
|
||||
pass # Timer wasn't registered, that's fine
|
||||
|
||||
# Re-register timer with slightly longer interval for stability
|
||||
bpy.app.timers.register(poll_threads, first_interval=0.1, persistent=True)
|
||||
|
||||
# Re-raise the original exception after ensuring timer continuity
|
||||
raise exception
|
||||
|
||||
def cleanup_polling_system() -> None:
|
||||
"""Optional cleanup function for proper shutdown."""
|
||||
global _active_threads, _consecutive_empty_polls
|
||||
|
||||
# Wait for remaining threads to complete (with timeout)
|
||||
for thread in list(_active_threads.keys()):
|
||||
if thread.is_alive():
|
||||
thread.join(timeout=1.0) # 1 second timeout
|
||||
|
||||
# Clear tracking structures
|
||||
_active_threads.clear()
|
||||
_consecutive_empty_polls = 0
|
||||
|
||||
# Unregister timer
|
||||
try:
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def get_polling_stats() -> dict:
|
||||
"""Get statistics about the polling system for monitoring."""
|
||||
return {
|
||||
'active_threads': len(_active_threads),
|
||||
'consecutive_empty_polls': _consecutive_empty_polls,
|
||||
'thread_ids': [t.ident for t in _active_threads.keys()]
|
||||
}
|
||||
return 0.01
|
||||
|
||||
|
||||
loaded_py_libraries: Dict[str, types.ModuleType] = {}
|
||||
|
||||
@ -727,7 +727,7 @@ _viewport_proc_build = None # Separate process tracker for viewport builds
|
||||
|
||||
def build_viewport(viewport_id, width=1920, height=1080):
|
||||
"""Build project for viewport"""
|
||||
global _viewport_build_in_progress, _viewport_pending_launches, profile_time
|
||||
global _viewport_build_in_progress, _viewport_pending_launches, profile_time, scripts_mtime
|
||||
|
||||
wrd = bpy.data.worlds.get('Lnx')
|
||||
if not wrd:
|
||||
@ -769,6 +769,35 @@ def build_viewport(viewport_id, width=1920, height=1080):
|
||||
state.is_publish = False
|
||||
state.is_export = False
|
||||
|
||||
# Recompile detection (matching play() behavior)
|
||||
if not wrd.lnx_cache_build or \
|
||||
not os.path.isfile(krom_js_path) or \
|
||||
assets.khafile_defs_last != assets.khafile_defs or \
|
||||
state.last_target != state.target:
|
||||
wrd.lnx_recompile = True
|
||||
|
||||
state.last_target = state.target
|
||||
|
||||
# Trait sources modified
|
||||
state.mod_scripts = []
|
||||
script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
|
||||
if os.path.isdir(script_path):
|
||||
new_mtime = scripts_mtime
|
||||
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
|
||||
mtime = os.path.getmtime(fn)
|
||||
if scripts_mtime < mtime:
|
||||
lnx.utils.fetch_script_props(fn)
|
||||
fn = fn.split('Sources/')[1]
|
||||
fn = fn[:-3] #.hx
|
||||
fn = fn.replace('/', '.')
|
||||
state.mod_scripts.append(fn)
|
||||
wrd.lnx_recompile = True
|
||||
if new_mtime < mtime:
|
||||
new_mtime = mtime
|
||||
scripts_mtime = new_mtime
|
||||
if len(state.mod_scripts) > 0:
|
||||
lnx.utils.fetch_trait_props()
|
||||
|
||||
log.clear(clear_warnings=True, clear_errors=True)
|
||||
|
||||
sdk_path = lnx.utils.get_sdk_path()
|
||||
@ -817,7 +846,8 @@ def compile_viewport(assets_only=False):
|
||||
if not wrd.lnx_verbose_output:
|
||||
cmd.append("--quiet")
|
||||
|
||||
if assets_only:
|
||||
krom_js_path = lnx.utils.build_dir() + '/debug/krom/krom.js'
|
||||
if assets_only and os.path.exists(krom_js_path):
|
||||
cmd.append('--nohaxe')
|
||||
cmd.append('--noproject')
|
||||
|
||||
@ -856,7 +886,7 @@ def viewport_build_done():
|
||||
|
||||
def play_viewport(viewport_id, width=1920, height=1080):
|
||||
"""Launch Krom in a viewport"""
|
||||
global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build
|
||||
global _viewport_build_in_progress, _viewport_pending_launches, _viewport_proc_build, scripts_mtime
|
||||
|
||||
if not viewport_id:
|
||||
log.error('No viewport_id: play_viewport requires an id')
|
||||
@ -895,9 +925,38 @@ def play_viewport(viewport_id, width=1920, height=1080):
|
||||
os.makedirs(sources_path)
|
||||
|
||||
# export data same as play() but without setting state.is_play
|
||||
state.is_viewport = True
|
||||
state.target = 'krom'
|
||||
state.is_publish = False
|
||||
state.is_export = False
|
||||
|
||||
if not wrd.lnx_cache_build or \
|
||||
not os.path.isfile(krom_js_path) or \
|
||||
assets.khafile_defs_last != assets.khafile_defs or \
|
||||
state.last_target != state.target:
|
||||
wrd.lnx_recompile = True
|
||||
|
||||
state.last_target = state.target
|
||||
|
||||
state.mod_scripts = []
|
||||
script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
|
||||
if os.path.isdir(script_path):
|
||||
new_mtime = scripts_mtime
|
||||
for fn in glob.iglob(os.path.join(script_path, '**', '*.hx'), recursive=True):
|
||||
mtime = os.path.getmtime(fn)
|
||||
if scripts_mtime < mtime:
|
||||
lnx.utils.fetch_script_props(fn)
|
||||
fn = fn.split('Sources/')[1]
|
||||
fn = fn[:-3] #.hx
|
||||
fn = fn.replace('/', '.')
|
||||
state.mod_scripts.append(fn)
|
||||
wrd.lnx_recompile = True
|
||||
if new_mtime < mtime:
|
||||
new_mtime = mtime
|
||||
scripts_mtime = new_mtime
|
||||
if len(state.mod_scripts) > 0:
|
||||
lnx.utils.fetch_trait_props()
|
||||
|
||||
export_data(fp, sdk_path)
|
||||
|
||||
compile_viewport(assets_only=(not wrd.lnx_recompile))
|
||||
|
||||
@ -11,6 +11,8 @@ import sys
|
||||
import os
|
||||
import array
|
||||
import atexit
|
||||
from mathutils import Quaternion, Matrix, Vector
|
||||
import lnx.make_state as state
|
||||
|
||||
HAS_GPU_STATE = bpy.app.version >= (3, 0, 0)
|
||||
if not HAS_GPU_STATE:
|
||||
@ -24,22 +26,6 @@ try:
|
||||
except ImportError:
|
||||
HAS_NUMPY = False
|
||||
|
||||
# TODO: Fix correctly
|
||||
def _build_srgb_to_linear_lut():
|
||||
lut = []
|
||||
for i in range(256):
|
||||
srgb = i / 255.0
|
||||
if srgb <= 0.04045:
|
||||
linear = srgb / 12.92
|
||||
else:
|
||||
linear = ((srgb + 0.055) / 1.055) ** 2.4
|
||||
lut.append(linear)
|
||||
return lut
|
||||
|
||||
_srgb_to_linear_lut_list = _build_srgb_to_linear_lut()
|
||||
if HAS_NUMPY:
|
||||
_srgb_to_linear_lut = np.array(_srgb_to_linear_lut_list, dtype=np.float32)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import mmap
|
||||
else:
|
||||
@ -50,8 +36,8 @@ else:
|
||||
except ImportError:
|
||||
HAS_POSIX_IPC = False
|
||||
|
||||
VIEWPORT_BUILD = 0x4B524F4D
|
||||
VIEWPORT_VERSION = 1
|
||||
VIEWPORT_BUILD = 0x52554E54
|
||||
VIEWPORT_VERSION = 2
|
||||
VIEWPORT_SHMEM_NAME_BASE = "KROM_VIEWPORT_FB"
|
||||
|
||||
def _get_viewport_shmem_name(viewport_id=None):
|
||||
@ -118,6 +104,7 @@ MOUSE_BUTTON_MIDDLE = 2
|
||||
_rendered_mode_pending = False
|
||||
_rendered_mode_retries = 0
|
||||
_msgbus_owner = object()
|
||||
_make_module = None
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def _on_depsgraph_update(scene, depsgraph):
|
||||
@ -184,9 +171,10 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
bl_idname = 'KROM_VIEWPORT'
|
||||
bl_label = 'Krom Viewport'
|
||||
bl_use_preview = False
|
||||
bl_use_gpu_context = True
|
||||
bl_use_shading_nodes_custom = False
|
||||
bl_use_eevee_viewport = True
|
||||
if bpy.app.version >= (2, 90, 0):
|
||||
bl_use_gpu_context = True
|
||||
bl_use_eevee_viewport = True
|
||||
|
||||
|
||||
def _ensure_initialized(self):
|
||||
@ -200,6 +188,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._initialized = False
|
||||
self._shader = None
|
||||
self._batch = None
|
||||
self._batch_dims = None
|
||||
self._viewport_init_done = True
|
||||
self._rendered_mode_set = False
|
||||
self._last_shading_mode = None
|
||||
@ -208,6 +197,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._krom_proc = None
|
||||
self._krom_launch_attempted = False
|
||||
self._pending_camera_sync = False
|
||||
self._input_enabled_state = None
|
||||
self._last_region_dims = None
|
||||
self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
|
||||
self._engine_id = id(self)
|
||||
# print(f"New engine instance created: {self._engine_id}")
|
||||
|
||||
@ -221,8 +213,34 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
if current != 'RENDERED':
|
||||
space.shading.type = 'RENDERED'
|
||||
self._rendered_mode_set = True
|
||||
# Disable viewport overlay so Krom output is unobstructed
|
||||
if space and hasattr(space, 'overlay'):
|
||||
if not hasattr(self, '_overlay_saved'):
|
||||
self._overlay_saved = space.overlay.show_overlays
|
||||
space.overlay.show_overlays = False
|
||||
except Exception as e:
|
||||
print(f"Failed to set rendered mode: {e}")
|
||||
|
||||
def _restore_overlay(self, context=None):
|
||||
try:
|
||||
if not hasattr(self, '_overlay_saved'):
|
||||
return
|
||||
spaces = []
|
||||
if context and context.space_data:
|
||||
spaces = [context.space_data]
|
||||
else:
|
||||
for window in bpy.context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
for sp in area.spaces:
|
||||
if sp.type == 'VIEW_3D':
|
||||
spaces.append(sp)
|
||||
for sp in spaces:
|
||||
if hasattr(sp, 'overlay'):
|
||||
sp.overlay.show_overlays = self._overlay_saved
|
||||
del self._overlay_saved
|
||||
except:
|
||||
pass
|
||||
|
||||
def _handle_mode_switch(self, context):
|
||||
"""Detect shading mode changes and restart Krom on Solid->Rendered switch if needed."""
|
||||
@ -326,6 +344,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
except:
|
||||
pass
|
||||
self._krom_proc = None
|
||||
self._krom_launch_attempted = False
|
||||
self._initialized = False
|
||||
self._cleanup_shared_memory()
|
||||
|
||||
def _init_shared_memory(self, context=None):
|
||||
self._ensure_initialized()
|
||||
@ -493,6 +514,27 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
self._initialized = False
|
||||
|
||||
def _peek_frame_id(self):
|
||||
"""Quick check if a new frame is available without reading full header."""
|
||||
self._ensure_initialized()
|
||||
if not self._initialized:
|
||||
return None
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
if not self._shm_ptr:
|
||||
return None
|
||||
frame_id = struct.unpack('<Q', ctypes.string_at(self._shm_ptr + OFFSET_FRAME_ID, 8))[0]
|
||||
ready_flag = struct.unpack('<I', ctypes.string_at(self._shm_ptr + OFFSET_READY_FLAG, 4))[0]
|
||||
return frame_id, ready_flag
|
||||
else:
|
||||
self._shm.seek(OFFSET_FRAME_ID)
|
||||
frame_id = struct.unpack('<Q', self._shm.read(8))[0]
|
||||
self._shm.seek(OFFSET_READY_FLAG)
|
||||
ready_flag = struct.unpack('<I', self._shm.read(4))[0]
|
||||
return frame_id, ready_flag
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _read_header(self):
|
||||
"""Read and parse the shared memory header."""
|
||||
self._ensure_initialized()
|
||||
@ -503,10 +545,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
if sys.platform == 'win32':
|
||||
if not self._shm_ptr:
|
||||
return None
|
||||
# use memmove for safety
|
||||
header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
|
||||
ctypes.memmove(header_buffer, self._shm_ptr, VIEWPORT_HEADER_SIZE)
|
||||
header_data = bytes(header_buffer)
|
||||
ctypes.memmove(self._header_buffer, self._shm_ptr, VIEWPORT_HEADER_SIZE)
|
||||
header_data = bytes(self._header_buffer)
|
||||
else:
|
||||
self._shm.seek(0)
|
||||
header_data = self._shm.read(VIEWPORT_HEADER_SIZE)
|
||||
@ -516,7 +556,6 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
ready_flag, fmt = struct.unpack('<II', header_data[24:32])
|
||||
|
||||
if build != VIEWPORT_BUILD:
|
||||
# shared memory may have been recreated
|
||||
self._handle_shmem_invalid()
|
||||
return None
|
||||
|
||||
@ -538,21 +577,30 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
"""Handle invalid/stale shared memory by cleaning up and allowing re-init."""
|
||||
self._cleanup_shared_memory()
|
||||
self._initialized = False
|
||||
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
|
||||
try:
|
||||
bgl.glDeleteTextures(1, self._gl_texture_buf)
|
||||
except:
|
||||
pass
|
||||
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
|
||||
self._texture = None
|
||||
self._last_frame_id = 0
|
||||
|
||||
def _read_framebuffer(self):
|
||||
"""Read framebuffer from shared memory."""
|
||||
# Fast path: peek frame_id and ready_flag without reading full header
|
||||
peek = self._peek_frame_id()
|
||||
if peek is None:
|
||||
return None
|
||||
frame_id, ready_flag = peek
|
||||
if frame_id == self._last_frame_id or ready_flag == 0:
|
||||
return None
|
||||
|
||||
# New frame available - read full header for dimensions
|
||||
header = self._read_header()
|
||||
if not header:
|
||||
return None
|
||||
|
||||
if header['frame_id'] == self._last_frame_id:
|
||||
return None
|
||||
|
||||
if header['ready_flag'] == 0:
|
||||
return None
|
||||
|
||||
width = header['width']
|
||||
height = header['height']
|
||||
|
||||
@ -565,15 +613,26 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
if sys.platform == 'win32':
|
||||
if not self._shm_ptr:
|
||||
return None
|
||||
pixel_buffer = (ctypes.c_ubyte * pixel_size)()
|
||||
ctypes.memmove(pixel_buffer, self._shm_ptr + VIEWPORT_HEADER_SIZE, pixel_size)
|
||||
pixels = bytes(pixel_buffer)
|
||||
if HAS_NUMPY:
|
||||
# Single copy: shared memory -> numpy array
|
||||
pixel_array = np.ctypeslib.as_array(
|
||||
(ctypes.c_ubyte * pixel_size).from_address(
|
||||
self._shm_ptr + VIEWPORT_HEADER_SIZE)
|
||||
).copy()
|
||||
else:
|
||||
pixel_buffer = (ctypes.c_ubyte * pixel_size)()
|
||||
ctypes.memmove(pixel_buffer, self._shm_ptr + VIEWPORT_HEADER_SIZE, pixel_size)
|
||||
pixel_array = bytes(pixel_buffer)
|
||||
|
||||
ready_bytes = struct.pack('<I', 0)
|
||||
ctypes.memmove(self._shm_ptr + OFFSET_READY_FLAG, ready_bytes, 4)
|
||||
else:
|
||||
self._shm.seek(VIEWPORT_HEADER_SIZE)
|
||||
pixels = self._shm.read(pixel_size)
|
||||
raw = self._shm.read(pixel_size)
|
||||
if HAS_NUMPY:
|
||||
pixel_array = np.frombuffer(raw, dtype=np.uint8).copy()
|
||||
else:
|
||||
pixel_array = raw
|
||||
self._shm.seek(OFFSET_READY_FLAG)
|
||||
self._shm.write(struct.pack('<I', 0))
|
||||
|
||||
@ -581,7 +640,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._width = width
|
||||
self._height = height
|
||||
|
||||
return pixels
|
||||
return pixel_array
|
||||
except Exception as e:
|
||||
self._handle_shmem_invalid()
|
||||
print(f"Failed to read framebuffer: {e}")
|
||||
@ -658,8 +717,6 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._shm.seek(OFFSET_KROM_CAMERA_DIRTY)
|
||||
self._shm.write(clear_data)
|
||||
|
||||
from mathutils import Quaternion, Matrix, Vector
|
||||
|
||||
rv3d = context.space_data.region_3d if context.space_data else None
|
||||
if not rv3d:
|
||||
return
|
||||
@ -734,6 +791,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._ensure_initialized()
|
||||
if not self._initialized:
|
||||
return
|
||||
if self._input_enabled_state == enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
data = struct.pack('<I', 1 if enabled else 0)
|
||||
@ -744,6 +803,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
else:
|
||||
self._shm.seek(OFFSET_INPUT_ENABLED)
|
||||
self._shm.write(data)
|
||||
self._input_enabled_state = enabled
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
@ -793,7 +853,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._shader = gpu.shader.from_builtin(SHADER_IMAGE)
|
||||
|
||||
def _update_texture(self, pixels, width, height):
|
||||
"""Create or update the GPU texture with new pixel data."""
|
||||
"""Create or update the GPU texture with new pixel data.
|
||||
Uses SRGB8_A8 format for GPU-side sRGB-to-linear conversion."""
|
||||
self._ensure_initialized()
|
||||
try:
|
||||
num_pixels = width * height * 4
|
||||
@ -801,28 +862,46 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
if len(pixels) < num_pixels:
|
||||
return
|
||||
|
||||
# clear old texture if dimensions changed
|
||||
if hasattr(self, '_tex_width') and hasattr(self, '_tex_height'):
|
||||
if self._tex_width != width or self._tex_height != height:
|
||||
self._texture = None
|
||||
# TODO: re-investigate LUT for linearization
|
||||
if HAS_NUMPY:
|
||||
pixel_array = np.frombuffer(pixels[:num_pixels], dtype=np.uint8).copy()
|
||||
pixel_array[3::4] = 255
|
||||
float_array = _srgb_to_linear_lut[pixel_array]
|
||||
float_array[3::4] = 1.0
|
||||
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
|
||||
else:
|
||||
pixel_array = array.array('B', pixels[:num_pixels])
|
||||
float_pixels = []
|
||||
for i, p in enumerate(pixel_array):
|
||||
if i % 4 == 3: # alpha channel
|
||||
float_pixels.append(1.0)
|
||||
else:
|
||||
float_pixels.append(_srgb_to_linear_lut_list[p])
|
||||
buffer = gpu.types.Buffer('FLOAT', len(float_pixels), float_pixels)
|
||||
if HAS_GPU_TEXTURE:
|
||||
if HAS_NUMPY:
|
||||
pixels[3::4] = 255
|
||||
float_array = pixels.astype(np.float32) / 255.0
|
||||
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
|
||||
else:
|
||||
pixel_array = array.array('B', pixels[:num_pixels])
|
||||
for i in range(3, len(pixel_array), 4):
|
||||
pixel_array[i] = 255
|
||||
float_pixels = [p / 255.0 for p in pixel_array]
|
||||
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_pixels)
|
||||
|
||||
self._texture = gpu.types.GPUTexture((width, height), format='SRGB8_A8', data=buffer)
|
||||
else:
|
||||
# bgl path for Blender < 3.0
|
||||
if not hasattr(self, '_gl_texture_buf') or self._gl_texture_buf[0] == 0:
|
||||
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
|
||||
bgl.glGenTextures(1, self._gl_texture_buf)
|
||||
tex_id = self._gl_texture_buf[0]
|
||||
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex_id)
|
||||
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
|
||||
bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
|
||||
|
||||
if HAS_NUMPY:
|
||||
pixels[3::4] = 255
|
||||
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, pixels.tobytes())
|
||||
else:
|
||||
pixel_array = array.array('B', pixels[:num_pixels])
|
||||
for i in range(3, len(pixel_array), 4):
|
||||
pixel_array[i] = 255
|
||||
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array))
|
||||
|
||||
internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8)
|
||||
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, internal_format, width, height, 0,
|
||||
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
||||
|
||||
self._texture = tex_id
|
||||
|
||||
self._texture = gpu.types.GPUTexture((width, height), format='RGBA32F', data=buffer)
|
||||
self._tex_width = width
|
||||
self._tex_height = height
|
||||
|
||||
@ -833,9 +912,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
def render(self, depsgraph):
|
||||
"""Disabled render method for final renders (F12)."""
|
||||
pass
|
||||
|
||||
def free(self):
|
||||
"""Called by Blender when the engine instance is destroyed."""
|
||||
self._stop_krom_process()
|
||||
self._restore_overlay()
|
||||
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
|
||||
try:
|
||||
bgl.glDeleteTextures(1, self._gl_texture_buf)
|
||||
except:
|
||||
pass
|
||||
if self._viewport_id and self._viewport_id in _active_krom_engines:
|
||||
del _active_krom_engines[self._viewport_id]
|
||||
|
||||
def view_update(self, context, depsgraph):
|
||||
"""Called when the scene is updated."""
|
||||
if state.is_exporting:
|
||||
return
|
||||
self._ensure_initialized()
|
||||
if not self._init_shared_memory(context):
|
||||
return
|
||||
@ -848,6 +941,11 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
def view_draw(self, context, depsgraph):
|
||||
"""Called to draw the viewport."""
|
||||
global _active_krom_engine, _active_krom_engines, _active_viewport_id, _input_operator_running, _make_module
|
||||
|
||||
if state.is_exporting:
|
||||
return
|
||||
|
||||
self._ensure_initialized()
|
||||
self._handle_mode_switch(context)
|
||||
|
||||
@ -872,10 +970,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
self._pending_camera_sync = False
|
||||
self._sync_camera_once(context)
|
||||
|
||||
# Check if Krom process has died - allow relaunch
|
||||
if self._krom_launch_attempted and self._viewport_id:
|
||||
if _make_module is None:
|
||||
import lnx.make
|
||||
_make_module = lnx.make
|
||||
proc = _make_module._viewport_processes.get(self._viewport_id)
|
||||
if proc is None or proc.poll() is not None:
|
||||
print(f"Krom process for viewport {self._viewport_id} is gone, resetting for relaunch")
|
||||
self._krom_launch_attempted = False
|
||||
self._initialized = False
|
||||
self._cleanup_shared_memory()
|
||||
if self._viewport_id in _active_krom_engines:
|
||||
del _active_krom_engines[self._viewport_id]
|
||||
|
||||
self._set_rendered_mode(context)
|
||||
|
||||
# TODO: input forwarding when viewport is active
|
||||
global _active_krom_engine, _active_krom_engines, _active_viewport_id, _input_operator_running
|
||||
_active_krom_engine = self # legacy single-engine reference
|
||||
|
||||
if self._viewport_id:
|
||||
@ -886,23 +997,19 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
if not _input_operator_running:
|
||||
bpy.app.timers.register(_start_input_capture, first_interval=0.1)
|
||||
else:
|
||||
if not hasattr(self, '_input_check_counter'):
|
||||
self._input_check_counter = 0
|
||||
self._input_check_counter += 1
|
||||
if self._input_check_counter > 60:
|
||||
self._input_check_counter = 0
|
||||
bpy.app.timers.register(_start_input_capture, first_interval=0.5)
|
||||
|
||||
region = context.region
|
||||
if region and region.width > 0 and region.height > 0:
|
||||
self._request_resize(region.width, region.height)
|
||||
dims = (region.width, region.height)
|
||||
if self._last_region_dims != dims:
|
||||
self._last_region_dims = dims
|
||||
self._request_resize(region.width, region.height)
|
||||
|
||||
self._read_krom_camera(context)
|
||||
|
||||
pixels = self._read_framebuffer()
|
||||
|
||||
if pixels:
|
||||
if pixels is not None:
|
||||
self._update_texture(pixels, self._width, self._height)
|
||||
|
||||
if not self._texture:
|
||||
@ -917,26 +1024,36 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
|
||||
region = context.region
|
||||
|
||||
vertices = (
|
||||
(0, 0),
|
||||
(region.width, 0),
|
||||
(region.width, region.height),
|
||||
(0, region.height),
|
||||
)
|
||||
tex_coords = (
|
||||
(0, 1), # Flip Y
|
||||
(1, 1),
|
||||
(1, 0),
|
||||
(0, 0),
|
||||
)
|
||||
indices = ((0, 1, 2), (0, 2, 3))
|
||||
dims = (region.width, region.height)
|
||||
if self._batch is None or self._batch_dims != dims:
|
||||
vertices = (
|
||||
(0, 0),
|
||||
(region.width, 0),
|
||||
(region.width, region.height),
|
||||
(0, region.height),
|
||||
)
|
||||
tex_coords = (
|
||||
(0, 1),
|
||||
(1, 1),
|
||||
(1, 0),
|
||||
(0, 0),
|
||||
)
|
||||
indices = ((0, 1, 2), (0, 2, 3))
|
||||
self._batch = batch_for_shader(self._shader, 'TRIS', {"pos": vertices, "texCoord": tex_coords}, indices=indices)
|
||||
self._batch_dims = dims
|
||||
|
||||
self._shader.bind()
|
||||
self._shader.uniform_sampler("image", self._texture)
|
||||
if HAS_GPU_TEXTURE:
|
||||
self._shader.uniform_sampler("image", self._texture)
|
||||
else:
|
||||
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._texture)
|
||||
self._shader.uniform_int("image", [0])
|
||||
|
||||
batch = batch_for_shader(self._shader, 'TRIS', {"pos": vertices, "texCoord": tex_coords}, indices=indices)
|
||||
batch.draw(self._shader)
|
||||
self._batch.draw(self._shader)
|
||||
|
||||
if not HAS_GPU_TEXTURE:
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
||||
|
||||
if HAS_GPU_STATE:
|
||||
gpu.state.blend_set('NONE')
|
||||
@ -969,12 +1086,14 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
||||
|
||||
batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
|
||||
batch.draw(shader)
|
||||
|
||||
|
||||
if HAS_GPU_STATE:
|
||||
gpu.state.blend_set('NONE')
|
||||
else:
|
||||
bgl.glDisable(bgl.GL_BLEND)
|
||||
|
||||
self.tag_redraw()
|
||||
|
||||
|
||||
class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
"""Modal operator to capture and forward input to Krom viewport"""
|
||||
@ -988,8 +1107,7 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
|
||||
def modal(self, context, event):
|
||||
global _input_operator_running
|
||||
|
||||
# check if we should stop
|
||||
|
||||
if event.type == 'ESC':
|
||||
try:
|
||||
if self._engine:
|
||||
@ -1002,22 +1120,13 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
if event.type in {'SCREEN_SET', 'SCREEN_CHANGED'}:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
if not context.space_data or not context.area:
|
||||
# Re-derive area/region/engine from mouse position each event
|
||||
result = self._find_viewport_under_mouse(event)
|
||||
if result is None:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
if context.space_data.type == 'VIEW_3D':
|
||||
if context.engine != 'KROM_VIEWPORT':
|
||||
try:
|
||||
if self._engine:
|
||||
self._engine._set_input_enabled(False)
|
||||
except ReferenceError:
|
||||
pass
|
||||
_input_operator_running = False
|
||||
return {'CANCELLED'}
|
||||
|
||||
current_engine = self._get_engine_for_area(context, event)
|
||||
if current_engine:
|
||||
self._engine = current_engine
|
||||
engine, area, region = result
|
||||
self._engine = engine
|
||||
|
||||
try:
|
||||
if not self._engine or not self._engine._initialized:
|
||||
@ -1026,37 +1135,19 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
_input_operator_running = False
|
||||
return {'CANCELLED'}
|
||||
|
||||
region = context.region
|
||||
if not region:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
# Check if mouse is over a UI region (toolbar, header, panel)
|
||||
mouse_x_abs = event.mouse_x
|
||||
mouse_y_abs = event.mouse_y
|
||||
for rgn in area.regions:
|
||||
if rgn.type == 'WINDOW':
|
||||
continue
|
||||
if (rgn.x <= mouse_x_abs <= rgn.x + rgn.width and
|
||||
rgn.y <= mouse_y_abs <= rgn.y + rgn.height):
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
if region.type != 'WINDOW':
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
mouse_in_region = (0 <= event.mouse_region_x <= region.width and
|
||||
0 <= event.mouse_region_y <= region.height)
|
||||
|
||||
if not mouse_in_region:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
area = context.area
|
||||
if area:
|
||||
mouse_x_abs = event.mouse_x
|
||||
mouse_y_abs = event.mouse_y
|
||||
for rgn in area.regions:
|
||||
# skip the main window region where we actually want input
|
||||
if rgn.type == 'WINDOW':
|
||||
continue
|
||||
# check if mouse is over toolbar, header, UI panel, etc.
|
||||
if (rgn.x <= mouse_x_abs <= rgn.x + rgn.width and
|
||||
rgn.y <= mouse_y_abs <= rgn.y + rgn.height):
|
||||
# mouse is over a UI region - pass through
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
mouse_x = event.mouse_region_x
|
||||
# flip Y for Krom
|
||||
mouse_y = region.height - event.mouse_region_y
|
||||
# Compute mouse position relative to the WINDOW region
|
||||
mouse_x = mouse_x_abs - region.x
|
||||
mouse_y = region.height - (mouse_y_abs - region.y)
|
||||
|
||||
try:
|
||||
if event.type == 'MOUSEMOVE':
|
||||
@ -1128,49 +1219,63 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
||||
}
|
||||
return key_map.get(blender_key)
|
||||
|
||||
def _get_engine_for_area(self, context, event=None):
|
||||
"""Get the correct engine for the viewport under the mouse cursor."""
|
||||
# Use mouse coordinates to find which viewport the mouse is over
|
||||
if event:
|
||||
mouse_x = event.mouse_x
|
||||
mouse_y = event.mouse_y
|
||||
def _find_viewport_under_mouse(self, event):
|
||||
"""Find the area, WINDOW region, and engine under the mouse cursor.
|
||||
Returns (engine, area, region) or None."""
|
||||
mouse_x = event.mouse_x
|
||||
mouse_y = event.mouse_y
|
||||
|
||||
for window in bpy.context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
if area.type != 'VIEW_3D':
|
||||
continue
|
||||
for window in bpy.context.window_manager.windows:
|
||||
for area in window.screen.areas:
|
||||
if area.type != 'VIEW_3D':
|
||||
continue
|
||||
|
||||
if (area.x <= mouse_x < area.x + area.width and
|
||||
if not (area.x <= mouse_x < area.x + area.width and
|
||||
area.y <= mouse_y < area.y + area.height):
|
||||
continue
|
||||
|
||||
for space in area.spaces:
|
||||
if space.type == 'VIEW_3D':
|
||||
try:
|
||||
viewport_id = hex(space.as_pointer())[-6:]
|
||||
if viewport_id in _active_krom_engines:
|
||||
return _active_krom_engines[viewport_id]
|
||||
except:
|
||||
pass
|
||||
# Find the WINDOW region for this area
|
||||
win_region = None
|
||||
for rgn in area.regions:
|
||||
if rgn.type == 'WINDOW':
|
||||
win_region = rgn
|
||||
break
|
||||
|
||||
if context.space_data:
|
||||
try:
|
||||
viewport_id = hex(context.space_data.as_pointer())[-6:]
|
||||
if viewport_id in _active_krom_engines:
|
||||
return _active_krom_engines[viewport_id]
|
||||
except:
|
||||
pass
|
||||
if not win_region:
|
||||
continue
|
||||
|
||||
if _active_viewport_id and _active_viewport_id in _active_krom_engines:
|
||||
return _active_krom_engines[_active_viewport_id]
|
||||
# Check if mouse is actually within the WINDOW region bounds
|
||||
if not (win_region.x <= mouse_x < win_region.x + win_region.width and
|
||||
win_region.y <= mouse_y < win_region.y + win_region.height):
|
||||
continue
|
||||
|
||||
return _active_krom_engine
|
||||
for space in area.spaces:
|
||||
if space.type == 'VIEW_3D':
|
||||
try:
|
||||
viewport_id = hex(space.as_pointer())[-6:]
|
||||
if viewport_id in _active_krom_engines:
|
||||
return (_active_krom_engines[viewport_id], area, win_region)
|
||||
except:
|
||||
pass
|
||||
break
|
||||
|
||||
return None
|
||||
|
||||
def invoke(self, context, event):
|
||||
self._engine = self._get_engine_for_area(context)
|
||||
if not self._engine:
|
||||
result = self._find_viewport_under_mouse(event)
|
||||
if result is None:
|
||||
# Fallback: use context's viewport
|
||||
if context.space_data:
|
||||
try:
|
||||
viewport_id = hex(context.space_data.as_pointer())[-6:]
|
||||
if viewport_id in _active_krom_engines:
|
||||
self._engine = _active_krom_engines[viewport_id]
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
except:
|
||||
pass
|
||||
return {'CANCELLED'}
|
||||
|
||||
self._engine = result[0]
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
@ -1192,8 +1297,12 @@ def _start_input_capture():
|
||||
for region in area.regions:
|
||||
if region.type == 'WINDOW':
|
||||
override = {'window': window, 'area': area, 'region': region}
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.krom.viewport_input('INVOKE_DEFAULT')
|
||||
if bpy.app.version >= (3, 0, 0):
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.krom.viewport_input('INVOKE_DEFAULT')
|
||||
_input_operator_running = True
|
||||
else:
|
||||
bpy.ops.krom.viewport_input(override, 'INVOKE_DEFAULT')
|
||||
_input_operator_running = True
|
||||
return None
|
||||
except Exception as e:
|
||||
@ -1208,7 +1317,9 @@ def get_panels():
|
||||
'VIEWLAYER_PT_layer_passes',
|
||||
}
|
||||
|
||||
compatible_engines = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
|
||||
compatible_engines = {'BLENDER_RENDER', 'BLENDER_EEVEE'}
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
compatible_engines.add('BLENDER_EEVEE_NEXT')
|
||||
|
||||
panels = []
|
||||
for panel in bpy.types.Panel.__subclasses__():
|
||||
@ -1262,6 +1373,12 @@ class KROM_OT_stop_viewport(bpy.types.Operator):
|
||||
make.stop_viewport(viewport_id)
|
||||
global _active_krom_engines
|
||||
if viewport_id in _active_krom_engines:
|
||||
engine = _active_krom_engines[viewport_id]
|
||||
try:
|
||||
engine._stop_krom_process()
|
||||
engine._restore_overlay(context)
|
||||
except:
|
||||
pass
|
||||
del _active_krom_engines[viewport_id]
|
||||
if context.space_data and hasattr(context.space_data, 'shading'):
|
||||
context.space_data.shading.type = 'SOLID'
|
||||
|
||||
Reference in New Issue
Block a user