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 windowWidth(id: Int): Int;
|
||||||
static function windowHeight(id: Int): Int;
|
static function windowHeight(id: Int): Int;
|
||||||
static function setWindowTitle(id: Int, title: String): Void;
|
static function setWindowTitle(id: Int, title: String): Void;
|
||||||
|
static function windowSetForeground(id: Int): Void;
|
||||||
static function screenDpi(): Int;
|
static function screenDpi(): Int;
|
||||||
static function systemId(): String;
|
static function systemId(): String;
|
||||||
static function requestShutdown(): Void;
|
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);
|
g.setImageTexture(voxel_tb2, rts.get(write_sdf).image);
|
||||||
|
|
||||||
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
|
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
|
||||||
for (i in 0...Main.voxelgiClipmapCount) {
|
for (j in 0...Main.voxelgiClipmapCount) {
|
||||||
fa[i * 10] = clipmaps[i].voxelSize;
|
fa[j * 10] = clipmaps[j].voxelSize;
|
||||||
fa[i * 10 + 1] = clipmaps[i].extents.x;
|
fa[j * 10 + 1] = clipmaps[j].extents.x;
|
||||||
fa[i * 10 + 2] = clipmaps[i].extents.y;
|
fa[j * 10 + 2] = clipmaps[j].extents.y;
|
||||||
fa[i * 10 + 3] = clipmaps[i].extents.z;
|
fa[j * 10 + 3] = clipmaps[j].extents.z;
|
||||||
fa[i * 10 + 4] = clipmaps[i].center.x;
|
fa[j * 10 + 4] = clipmaps[j].center.x;
|
||||||
fa[i * 10 + 5] = clipmaps[i].center.y;
|
fa[j * 10 + 5] = clipmaps[j].center.y;
|
||||||
fa[i * 10 + 6] = clipmaps[i].center.z;
|
fa[j * 10 + 6] = clipmaps[j].center.z;
|
||||||
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
|
fa[j * 10 + 7] = clipmaps[j].offset_prev.x;
|
||||||
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
|
fa[j * 10 + 8] = clipmaps[j].offset_prev.y;
|
||||||
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
|
fa[j * 10 + 9] = clipmaps[j].offset_prev.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setFloats(voxel_ca2, fa);
|
g.setFloats(voxel_ca2, fa);
|
||||||
@ -1407,17 +1407,17 @@ class Inc {
|
|||||||
g.setTexture(voxel_tg5, rts.get("voxelsSDF").image);
|
g.setTexture(voxel_tg5, rts.get("voxelsSDF").image);
|
||||||
|
|
||||||
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
|
var fa:Float32Array = new Float32Array(Main.voxelgiClipmapCount * 10);
|
||||||
for (i in 0...Main.voxelgiClipmapCount) {
|
for (j in 0...Main.voxelgiClipmapCount) {
|
||||||
fa[i * 10] = clipmaps[i].voxelSize;
|
fa[j * 10] = clipmaps[j].voxelSize;
|
||||||
fa[i * 10 + 1] = clipmaps[i].extents.x;
|
fa[j * 10 + 1] = clipmaps[j].extents.x;
|
||||||
fa[i * 10 + 2] = clipmaps[i].extents.y;
|
fa[j * 10 + 2] = clipmaps[j].extents.y;
|
||||||
fa[i * 10 + 3] = clipmaps[i].extents.z;
|
fa[j * 10 + 3] = clipmaps[j].extents.z;
|
||||||
fa[i * 10 + 4] = clipmaps[i].center.x;
|
fa[j * 10 + 4] = clipmaps[j].center.x;
|
||||||
fa[i * 10 + 5] = clipmaps[i].center.y;
|
fa[j * 10 + 5] = clipmaps[j].center.y;
|
||||||
fa[i * 10 + 6] = clipmaps[i].center.z;
|
fa[j * 10 + 6] = clipmaps[j].center.z;
|
||||||
fa[i * 10 + 7] = clipmaps[i].offset_prev.x;
|
fa[j * 10 + 7] = clipmaps[j].offset_prev.x;
|
||||||
fa[i * 10 + 8] = clipmaps[i].offset_prev.y;
|
fa[j * 10 + 8] = clipmaps[j].offset_prev.y;
|
||||||
fa[i * 10 + 9] = clipmaps[i].offset_prev.z;
|
fa[j * 10 + 9] = clipmaps[j].offset_prev.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setFloats(voxel_ca5, fa);
|
g.setFloats(voxel_ca5, fa);
|
||||||
|
|||||||
@ -415,9 +415,8 @@ class RenderPathDeferred {
|
|||||||
t.width = 0;
|
t.width = 0;
|
||||||
t.height = 0;
|
t.height = 0;
|
||||||
t.displayp = Inc.getDisplayp();
|
t.displayp = Inc.getDisplayp();
|
||||||
t.format = Inc.getHdrFormat();
|
t.format = "RGBA64";
|
||||||
t.scale = Inc.getSuperSampling();
|
t.scale = Inc.getSuperSampling();
|
||||||
t.depth_buffer = "main";
|
|
||||||
path.createRenderTarget(t);
|
path.createRenderTarget(t);
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
@ -869,6 +868,7 @@ class RenderPathDeferred {
|
|||||||
|
|
||||||
#if rp_water
|
#if rp_water
|
||||||
{
|
{
|
||||||
|
path.setDepthFrom("tex", "gbuffer1");
|
||||||
path.setTarget("buf");
|
path.setTarget("buf");
|
||||||
path.bindTarget("tex", "tex");
|
path.bindTarget("tex", "tex");
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||||
@ -876,6 +876,7 @@ class RenderPathDeferred {
|
|||||||
path.bindTarget("_main", "gbufferD");
|
path.bindTarget("_main", "gbufferD");
|
||||||
path.bindTarget("buf", "tex");
|
path.bindTarget("buf", "tex");
|
||||||
path.drawShader("shader_datas/water_pass/water_pass");
|
path.drawShader("shader_datas/water_pass/water_pass");
|
||||||
|
path.setDepthFrom("tex", "gbuffer0");
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
@ -951,20 +952,34 @@ class RenderPathDeferred {
|
|||||||
{
|
{
|
||||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
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
|
//save depth
|
||||||
path.setTarget("gbufferD1");
|
path.setTarget("gbufferD1");
|
||||||
path.bindTarget("_main", "tex");
|
path.bindTarget("_main", "tex");
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
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
|
//save background color
|
||||||
path.setTarget("refr");
|
path.setTarget("refr");
|
||||||
path.bindTarget("tex", "tex");
|
path.bindTarget("tex", "tex");
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||||
|
|
||||||
path.setTarget("gbuffer0", ["tex", "gbuffer_refraction",
|
path.setTarget("gbuffer0", ["gbuffer1", "gbuffer_refraction"]);
|
||||||
#if rp_gbuffer2 "gbuffer1", #end
|
|
||||||
#if rp_gbuffer_emission "buf", #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_shadowmap
|
#if rp_shadowmap
|
||||||
{
|
{
|
||||||
@ -976,32 +991,21 @@ class RenderPathDeferred {
|
|||||||
}
|
}
|
||||||
#end
|
#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
|
#if rp_ssrs
|
||||||
path.bindTarget("_main", "gbufferD");
|
path.bindTarget("_main", "gbufferD");
|
||||||
#end
|
#end
|
||||||
|
|
||||||
path.drawMeshes("refraction");
|
path.drawMeshes("refraction");
|
||||||
|
|
||||||
path.setTarget("buf");
|
path.setTarget("tex");
|
||||||
path.bindTarget("tex", "tex"); // scene with refractive objects
|
path.bindTarget("refr", "tex");
|
||||||
path.bindTarget("refr", "tex1"); // background without refractive objects
|
|
||||||
path.bindTarget("_main", "gbufferD");
|
|
||||||
path.bindTarget("gbufferD1", "gbufferD1");
|
path.bindTarget("gbufferD1", "gbufferD1");
|
||||||
path.bindTarget("gbuffer0", "gbuffer0");
|
path.bindTarget("gbuffer0", "gbuffer0");
|
||||||
|
path.bindTarget("gbuffer1", "tex1");
|
||||||
|
path.bindTarget("_main", "gbufferD");
|
||||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||||
|
|
||||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||||
|
|
||||||
path.setTarget("tex");
|
|
||||||
path.bindTarget("buf", "tex");
|
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
@ -1168,8 +1172,10 @@ class RenderPathDeferred {
|
|||||||
#else
|
#else
|
||||||
path.isProbe ? path.setTarget(framebuffer) : path.setTarget("bufa");
|
path.isProbe ? path.setTarget(framebuffer) : path.setTarget("bufa");
|
||||||
#end
|
#end
|
||||||
|
#elseif (rp_supersampling == 4)
|
||||||
|
path.setTarget("bufa");
|
||||||
#elseif rp_fsr1
|
#elseif rp_fsr1
|
||||||
path.setTarget("buf");
|
path.setTarget("bufa");
|
||||||
#else
|
#else
|
||||||
path.setTarget(framebuffer);
|
path.setTarget(framebuffer);
|
||||||
#end
|
#end
|
||||||
@ -1182,21 +1188,36 @@ class RenderPathDeferred {
|
|||||||
#end
|
#end
|
||||||
path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend");
|
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 (rp_antialiasing == "TAA")
|
||||||
{
|
{
|
||||||
if (!path.isProbe) { // No last frame for probe
|
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("bufa", "tex");
|
||||||
path.bindTarget("taa", "tex2");
|
path.bindTarget("taa", "tex2");
|
||||||
path.bindTarget("gbuffer2", "sveloc");
|
path.bindTarget("gbuffer2", "sveloc");
|
||||||
path.drawShader("shader_datas/taa_pass/taa_pass");
|
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
|
#if rp_fsr1
|
||||||
path.setTarget("buf");
|
path.setTarget("buf");
|
||||||
#else
|
#else
|
||||||
path.setTarget(framebuffer);
|
path.setTarget(framebuffer);
|
||||||
#end
|
#end
|
||||||
path.bindTarget("taa", "tex");
|
path.bindTarget("bufb", "tex");
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1220,7 +1241,7 @@ class RenderPathDeferred {
|
|||||||
var finalTarget = "";
|
var finalTarget = "";
|
||||||
path.setTarget(finalTarget);
|
path.setTarget(finalTarget);
|
||||||
path.bindTarget(framebuffer, "tex");
|
path.bindTarget(framebuffer, "tex");
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
path.drawShader("shader_datas/supersample_resolve/supersample_resolve");
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
|
|||||||
@ -147,7 +147,7 @@ class RenderPathForward {
|
|||||||
t.width = 0;
|
t.width = 0;
|
||||||
t.height = 0;
|
t.height = 0;
|
||||||
t.displayp = Inc.getDisplayp();
|
t.displayp = Inc.getDisplayp();
|
||||||
t.format = "DEPTH24";
|
t.format = "R32";
|
||||||
t.scale = Inc.getSuperSampling();
|
t.scale = Inc.getSuperSampling();
|
||||||
path.createRenderTarget(t);
|
path.createRenderTarget(t);
|
||||||
|
|
||||||
@ -446,6 +446,18 @@ class RenderPathForward {
|
|||||||
|
|
||||||
path.bindTarget("voxels", "voxels");
|
path.bindTarget("voxels", "voxels");
|
||||||
|
|
||||||
|
#if rp_shadowmap
|
||||||
|
{
|
||||||
|
#if lnx_shadowmap_atlas
|
||||||
|
Inc.bindShadowMapAtlas();
|
||||||
|
#else
|
||||||
|
Inc.bindShadowMap();
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
path.drawMeshes("voxel");
|
||||||
|
|
||||||
Inc.computeVoxelsTemporal();
|
Inc.computeVoxelsTemporal();
|
||||||
|
|
||||||
#if (rp_voxels == "Voxel GI")
|
#if (rp_voxels == "Voxel GI")
|
||||||
@ -473,7 +485,7 @@ class RenderPathForward {
|
|||||||
#if (rp_ssrefr || lnx_voxelgi_refract)
|
#if (rp_ssrefr || lnx_voxelgi_refract)
|
||||||
{
|
{
|
||||||
path.setTarget("gbuffer_refraction");
|
path.setTarget("gbuffer_refraction");
|
||||||
path.clearTarget(0xffff00ff);
|
path.clearTarget(0xffffff00);
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
@ -544,11 +556,21 @@ class RenderPathForward {
|
|||||||
{
|
{
|
||||||
if (leenkx.data.Config.raw.rp_ssrefr != false)
|
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
|
//save depth
|
||||||
path.setTarget("gbufferD1");
|
path.setTarget("gbufferD1");
|
||||||
path.bindTarget("_main", "tex");
|
path.bindTarget("_main", "tex");
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
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
|
//save background color
|
||||||
path.setTarget("refr");
|
path.setTarget("refr");
|
||||||
path.bindTarget("lbuffer0", "tex");
|
path.bindTarget("lbuffer0", "tex");
|
||||||
@ -579,18 +601,17 @@ class RenderPathForward {
|
|||||||
|
|
||||||
path.drawMeshes("refraction");
|
path.drawMeshes("refraction");
|
||||||
|
|
||||||
path.setTarget("bufa");
|
path.setTarget("lbuffer0");
|
||||||
|
|
||||||
path.bindTarget("lbuffer0", "tex");
|
path.bindTarget("lbuffer0", "tex");
|
||||||
path.bindTarget("refr", "tex1");
|
path.bindTarget("refr", "tex1");
|
||||||
path.bindTarget("_main", "gbufferD");
|
path.bindTarget("_main", "gbufferD");
|
||||||
path.bindTarget("gbufferD1", "gbufferD1");
|
path.bindTarget("gbufferD1", "gbufferD1");
|
||||||
path.bindTarget("lbuffer1", "gbuffer0");
|
path.bindTarget("lbuffer1", "gbuffer0");
|
||||||
|
path.bindTarget("lbuffer0", "gbuffer1");
|
||||||
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
path.bindTarget("gbuffer_refraction", "gbuffer_refraction");
|
||||||
|
|
||||||
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
path.drawShader("shader_datas/ssrefr_pass/ssrefr_pass");
|
||||||
path.setTarget("lbuffer0");
|
|
||||||
path.bindTarget("bufa", "tex");
|
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
@ -630,53 +651,6 @@ class RenderPathForward {
|
|||||||
}
|
}
|
||||||
#end
|
#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
|
#if rp_bloom
|
||||||
{
|
{
|
||||||
inline Inc.drawBloom("lbuffer0", bloomDownsampler, bloomUpsampler);
|
inline Inc.drawBloom("lbuffer0", bloomDownsampler, bloomUpsampler);
|
||||||
@ -718,8 +692,10 @@ class RenderPathForward {
|
|||||||
|
|
||||||
#if rp_water
|
#if rp_water
|
||||||
{
|
{
|
||||||
|
#if (!kha_opengl)
|
||||||
path.setDepthFrom("lbuffer0", "bufa"); // Unbind depth so we can read it
|
path.setDepthFrom("lbuffer0", "bufa"); // Unbind depth so we can read it
|
||||||
path.depthToRenderTarget.set("main", path.renderTargets.get("buf"));
|
path.depthToRenderTarget.set("main", path.renderTargets.get("buf"));
|
||||||
|
#end
|
||||||
|
|
||||||
path.setTarget("bufa");
|
path.setTarget("bufa");
|
||||||
path.bindTarget("lbuffer0", "tex");
|
path.bindTarget("lbuffer0", "tex");
|
||||||
@ -730,8 +706,10 @@ class RenderPathForward {
|
|||||||
path.bindTarget("bufa", "tex");
|
path.bindTarget("bufa", "tex");
|
||||||
path.drawShader("shader_datas/water_pass/water_pass");
|
path.drawShader("shader_datas/water_pass/water_pass");
|
||||||
|
|
||||||
|
#if (!kha_opengl)
|
||||||
path.setDepthFrom("lbuffer0", "buf"); // Re-bind depth
|
path.setDepthFrom("lbuffer0", "buf"); // Re-bind depth
|
||||||
path.depthToRenderTarget.set("main", path.renderTargets.get("lbuffer0"));
|
path.depthToRenderTarget.set("main", path.renderTargets.get("lbuffer0"));
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
@ -794,10 +772,20 @@ class RenderPathForward {
|
|||||||
path.bindTarget("bufa", "edgesTex");
|
path.bindTarget("bufa", "edgesTex");
|
||||||
path.drawShader("shader_datas/smaa_blend_weight/smaa_blend_weight");
|
path.drawShader("shader_datas/smaa_blend_weight/smaa_blend_weight");
|
||||||
|
|
||||||
|
#if (rp_supersampling == 4)
|
||||||
|
path.setTarget("bufa");
|
||||||
|
#else
|
||||||
path.setTarget(framebuffer);
|
path.setTarget(framebuffer);
|
||||||
|
#end
|
||||||
path.bindTarget("buf", "colorTex");
|
path.bindTarget("buf", "colorTex");
|
||||||
path.bindTarget("bufb", "blendTex");
|
path.bindTarget("bufb", "blendTex");
|
||||||
path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend");
|
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
|
#end
|
||||||
|
|
||||||
@ -805,17 +793,16 @@ class RenderPathForward {
|
|||||||
{
|
{
|
||||||
// FSR1 RCAS sharpening pass applied after AA, expects sRGB [0-1] input
|
// FSR1 RCAS sharpening pass applied after AA, expects sRGB [0-1] input
|
||||||
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
#if ((rp_antialiasing == "SMAA") || (rp_antialiasing == "TAA"))
|
||||||
#if (rp_supersampling == 4)
|
// SMAA outputs to framebuffer which needs an intermediate buffer
|
||||||
var fsrSource = "buf";
|
path.setTarget("bufb");
|
||||||
var fsrDest = "buf";
|
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
|
||||||
#else
|
path.drawShader("shader_datas/copy_pass/copy_pass");
|
||||||
// SMAA outputs to framebuffer which needs an intermediate buffer
|
var fsrSource = "bufb";
|
||||||
path.setTarget("bufb");
|
#if (rp_supersampling == 4)
|
||||||
path.bindTarget(framebuffer != "" ? framebuffer : "buf", "tex");
|
var fsrDest = "buf";
|
||||||
path.drawShader("shader_datas/copy_pass/copy_pass");
|
#else
|
||||||
var fsrSource = "bufb";
|
var fsrDest = "";
|
||||||
var fsrDest = "";
|
#end
|
||||||
#end
|
|
||||||
#else
|
#else
|
||||||
#if (rp_supersampling == 4)
|
#if (rp_supersampling == 4)
|
||||||
var fsrSource = "buf";
|
var fsrSource = "buf";
|
||||||
|
|||||||
@ -2198,25 +2198,28 @@ class LeenkxExporter:
|
|||||||
light_object = light_objects[0] if len(light_objects) > 0 else None
|
light_object = light_objects[0] if len(light_objects) > 0 else None
|
||||||
objtype = light_ref.type
|
objtype = light_ref.type
|
||||||
color = [light_ref.color[0], light_ref.color[1], light_ref.color[2]]
|
color = [light_ref.color[0], light_ref.color[1], light_ref.color[2]]
|
||||||
if light_ref.use_temperature:
|
if bpy.app.version >= (4, 5, 0):
|
||||||
temperature_color = light_ref.temperature_color
|
if light_ref.use_temperature:
|
||||||
color[0] *= temperature_color[0]
|
temperature_color = light_ref.temperature_color
|
||||||
color[1] *= temperature_color[1]
|
color[0] *= temperature_color[0]
|
||||||
color[2] *= temperature_color[2]
|
color[1] *= temperature_color[1]
|
||||||
|
color[2] *= temperature_color[2]
|
||||||
|
|
||||||
strength = light_ref.energy * math.pow(2.0, light_ref.exposure)
|
strength = light_ref.energy * math.pow(2.0, light_ref.exposure)
|
||||||
if not light_ref.normalize:
|
if not light_ref.normalize:
|
||||||
area = 0.0
|
area = 0.0
|
||||||
try:
|
try:
|
||||||
if light_object is not None:
|
if light_object is not None:
|
||||||
area = light_ref.area(matrix_world=light_object.matrix_world)
|
area = light_ref.area(matrix_world=light_object.matrix_world)
|
||||||
else:
|
else:
|
||||||
|
area = light_ref.area()
|
||||||
|
except TypeError:
|
||||||
area = light_ref.area()
|
area = light_ref.area()
|
||||||
except TypeError:
|
|
||||||
area = light_ref.area()
|
|
||||||
|
|
||||||
if area > 0.0:
|
if area > 0.0:
|
||||||
strength *= area
|
strength *= area
|
||||||
|
else:
|
||||||
|
strength = light_ref.energy
|
||||||
|
|
||||||
out_light = {
|
out_light = {
|
||||||
'name': object_ref[1]["structName"],
|
'name': object_ref[1]["structName"],
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import os
|
|||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
|
||||||
import types
|
import types
|
||||||
from typing import Dict, Tuple, Callable, Set
|
from typing import Dict, Tuple, Callable, Set
|
||||||
|
|
||||||
@ -13,6 +12,7 @@ from bpy.app.handlers import persistent
|
|||||||
import lnx
|
import lnx
|
||||||
import lnx.api
|
import lnx.api
|
||||||
import lnx.nodes_logic
|
import lnx.nodes_logic
|
||||||
|
import lnx.render_engine
|
||||||
import lnx.make_state as state
|
import lnx.make_state as state
|
||||||
import lnx.utils
|
import lnx.utils
|
||||||
import lnx.utils_vs
|
import lnx.utils_vs
|
||||||
@ -25,6 +25,7 @@ if lnx.is_reload(__name__):
|
|||||||
log = lnx.reload_module(log)
|
log = lnx.reload_module(log)
|
||||||
lnx_nodes = lnx.reload_module(lnx_nodes)
|
lnx_nodes = lnx.reload_module(lnx_nodes)
|
||||||
lnx.nodes_logic = lnx.reload_module(lnx.nodes_logic)
|
lnx.nodes_logic = lnx.reload_module(lnx.nodes_logic)
|
||||||
|
lnx.render_engine = lnx.reload_module(lnx.render_engine)
|
||||||
make = lnx.reload_module(make)
|
make = lnx.reload_module(make)
|
||||||
state = lnx.reload_module(state)
|
state = lnx.reload_module(state)
|
||||||
props = lnx.reload_module(props)
|
props = lnx.reload_module(props)
|
||||||
@ -33,10 +34,8 @@ if lnx.is_reload(__name__):
|
|||||||
else:
|
else:
|
||||||
lnx.enable_reload(__name__)
|
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] = {}
|
_active_threads: Dict[threading.Thread, Callable] = {}
|
||||||
_last_poll_time = 0.0
|
|
||||||
_consecutive_empty_polls = 0
|
|
||||||
_last_render_engine = None
|
_last_render_engine = None
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
@ -169,6 +168,14 @@ def check_render_engine() -> float:
|
|||||||
|
|
||||||
elif _last_render_engine == 'KROM_VIEWPORT':
|
elif _last_render_engine == 'KROM_VIEWPORT':
|
||||||
try:
|
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()
|
make.stop_viewport()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warn(f'Failed to stop viewport: {e}')
|
log.warn(f'Failed to stop viewport: {e}')
|
||||||
@ -183,113 +190,31 @@ def check_render_engine() -> float:
|
|||||||
|
|
||||||
|
|
||||||
def poll_threads() -> float:
|
def poll_threads() -> float:
|
||||||
"""
|
"""Polls the thread callback queue and processes completed threads."""
|
||||||
Improved thread polling with:
|
# Drain queue into active threads
|
||||||
- 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
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
thread, callback = make.thread_callback_queue.get(block=False)
|
thread, callback = make.thread_callback_queue.get(block=False)
|
||||||
_active_threads[thread] = callback
|
_active_threads[thread] = callback
|
||||||
new_threads_added += 1
|
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Early return if no active threads
|
|
||||||
if not _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
|
return 0.25
|
||||||
|
|
||||||
# Reset empty poll counter when we have active threads
|
# Join and callback all completed threads
|
||||||
_consecutive_empty_polls = 0
|
|
||||||
|
|
||||||
# Find completed threads (single pass, no re-queuing)
|
|
||||||
completed_threads = []
|
|
||||||
for thread in list(_active_threads.keys()):
|
for thread in list(_active_threads.keys()):
|
||||||
if not thread.is_alive():
|
if not thread.is_alive():
|
||||||
completed_threads.append(thread)
|
callback = _active_threads.pop(thread)
|
||||||
|
try:
|
||||||
# Batch process all completed threads
|
thread.join()
|
||||||
if completed_threads:
|
callback()
|
||||||
_process_completed_threads(completed_threads)
|
except Exception as e:
|
||||||
|
bpy.app.timers.unregister(poll_threads)
|
||||||
# Adaptive timing based on activity level
|
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
|
||||||
active_count = len(_active_threads)
|
raise e
|
||||||
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
|
|
||||||
|
|
||||||
def _process_completed_threads(completed_threads: list) -> None:
|
return 0.01
|
||||||
"""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()]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
loaded_py_libraries: Dict[str, types.ModuleType] = {}
|
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):
|
def build_viewport(viewport_id, width=1920, height=1080):
|
||||||
"""Build project for viewport"""
|
"""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')
|
wrd = bpy.data.worlds.get('Lnx')
|
||||||
if not wrd:
|
if not wrd:
|
||||||
@ -769,6 +769,35 @@ def build_viewport(viewport_id, width=1920, height=1080):
|
|||||||
state.is_publish = False
|
state.is_publish = False
|
||||||
state.is_export = 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)
|
log.clear(clear_warnings=True, clear_errors=True)
|
||||||
|
|
||||||
sdk_path = lnx.utils.get_sdk_path()
|
sdk_path = lnx.utils.get_sdk_path()
|
||||||
@ -817,7 +846,8 @@ def compile_viewport(assets_only=False):
|
|||||||
if not wrd.lnx_verbose_output:
|
if not wrd.lnx_verbose_output:
|
||||||
cmd.append("--quiet")
|
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('--nohaxe')
|
||||||
cmd.append('--noproject')
|
cmd.append('--noproject')
|
||||||
|
|
||||||
@ -856,7 +886,7 @@ def viewport_build_done():
|
|||||||
|
|
||||||
def play_viewport(viewport_id, width=1920, height=1080):
|
def play_viewport(viewport_id, width=1920, height=1080):
|
||||||
"""Launch Krom in a viewport"""
|
"""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:
|
if not viewport_id:
|
||||||
log.error('No viewport_id: play_viewport requires an 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)
|
os.makedirs(sources_path)
|
||||||
|
|
||||||
# export data same as play() but without setting state.is_play
|
# export data same as play() but without setting state.is_play
|
||||||
|
state.is_viewport = True
|
||||||
state.target = 'krom'
|
state.target = 'krom'
|
||||||
state.is_publish = False
|
state.is_publish = False
|
||||||
state.is_export = 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)
|
export_data(fp, sdk_path)
|
||||||
|
|
||||||
compile_viewport(assets_only=(not wrd.lnx_recompile))
|
compile_viewport(assets_only=(not wrd.lnx_recompile))
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import array
|
import array
|
||||||
import atexit
|
import atexit
|
||||||
|
from mathutils import Quaternion, Matrix, Vector
|
||||||
|
import lnx.make_state as state
|
||||||
|
|
||||||
HAS_GPU_STATE = bpy.app.version >= (3, 0, 0)
|
HAS_GPU_STATE = bpy.app.version >= (3, 0, 0)
|
||||||
if not HAS_GPU_STATE:
|
if not HAS_GPU_STATE:
|
||||||
@ -24,22 +26,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_NUMPY = False
|
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':
|
if sys.platform == 'win32':
|
||||||
import mmap
|
import mmap
|
||||||
else:
|
else:
|
||||||
@ -50,8 +36,8 @@ else:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_POSIX_IPC = False
|
HAS_POSIX_IPC = False
|
||||||
|
|
||||||
VIEWPORT_BUILD = 0x4B524F4D
|
VIEWPORT_BUILD = 0x52554E54
|
||||||
VIEWPORT_VERSION = 1
|
VIEWPORT_VERSION = 2
|
||||||
VIEWPORT_SHMEM_NAME_BASE = "KROM_VIEWPORT_FB"
|
VIEWPORT_SHMEM_NAME_BASE = "KROM_VIEWPORT_FB"
|
||||||
|
|
||||||
def _get_viewport_shmem_name(viewport_id=None):
|
def _get_viewport_shmem_name(viewport_id=None):
|
||||||
@ -118,6 +104,7 @@ MOUSE_BUTTON_MIDDLE = 2
|
|||||||
_rendered_mode_pending = False
|
_rendered_mode_pending = False
|
||||||
_rendered_mode_retries = 0
|
_rendered_mode_retries = 0
|
||||||
_msgbus_owner = object()
|
_msgbus_owner = object()
|
||||||
|
_make_module = None
|
||||||
|
|
||||||
@bpy.app.handlers.persistent
|
@bpy.app.handlers.persistent
|
||||||
def _on_depsgraph_update(scene, depsgraph):
|
def _on_depsgraph_update(scene, depsgraph):
|
||||||
@ -184,9 +171,10 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
bl_idname = 'KROM_VIEWPORT'
|
bl_idname = 'KROM_VIEWPORT'
|
||||||
bl_label = 'Krom Viewport'
|
bl_label = 'Krom Viewport'
|
||||||
bl_use_preview = False
|
bl_use_preview = False
|
||||||
bl_use_gpu_context = True
|
|
||||||
bl_use_shading_nodes_custom = False
|
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):
|
def _ensure_initialized(self):
|
||||||
@ -200,6 +188,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._initialized = False
|
self._initialized = False
|
||||||
self._shader = None
|
self._shader = None
|
||||||
self._batch = None
|
self._batch = None
|
||||||
|
self._batch_dims = None
|
||||||
self._viewport_init_done = True
|
self._viewport_init_done = True
|
||||||
self._rendered_mode_set = False
|
self._rendered_mode_set = False
|
||||||
self._last_shading_mode = None
|
self._last_shading_mode = None
|
||||||
@ -208,6 +197,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._krom_proc = None
|
self._krom_proc = None
|
||||||
self._krom_launch_attempted = False
|
self._krom_launch_attempted = False
|
||||||
self._pending_camera_sync = 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)
|
self._engine_id = id(self)
|
||||||
# print(f"New engine instance created: {self._engine_id}")
|
# print(f"New engine instance created: {self._engine_id}")
|
||||||
|
|
||||||
@ -221,8 +213,34 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
if current != 'RENDERED':
|
if current != 'RENDERED':
|
||||||
space.shading.type = 'RENDERED'
|
space.shading.type = 'RENDERED'
|
||||||
self._rendered_mode_set = True
|
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:
|
except Exception as e:
|
||||||
print(f"Failed to set rendered mode: {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):
|
def _handle_mode_switch(self, context):
|
||||||
"""Detect shading mode changes and restart Krom on Solid->Rendered switch if needed."""
|
"""Detect shading mode changes and restart Krom on Solid->Rendered switch if needed."""
|
||||||
@ -326,6 +344,9 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self._krom_proc = None
|
self._krom_proc = None
|
||||||
|
self._krom_launch_attempted = False
|
||||||
|
self._initialized = False
|
||||||
|
self._cleanup_shared_memory()
|
||||||
|
|
||||||
def _init_shared_memory(self, context=None):
|
def _init_shared_memory(self, context=None):
|
||||||
self._ensure_initialized()
|
self._ensure_initialized()
|
||||||
@ -493,6 +514,27 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
|
|
||||||
self._initialized = False
|
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):
|
def _read_header(self):
|
||||||
"""Read and parse the shared memory header."""
|
"""Read and parse the shared memory header."""
|
||||||
self._ensure_initialized()
|
self._ensure_initialized()
|
||||||
@ -503,10 +545,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
if not self._shm_ptr:
|
if not self._shm_ptr:
|
||||||
return None
|
return None
|
||||||
# use memmove for safety
|
ctypes.memmove(self._header_buffer, self._shm_ptr, VIEWPORT_HEADER_SIZE)
|
||||||
header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
|
header_data = bytes(self._header_buffer)
|
||||||
ctypes.memmove(header_buffer, self._shm_ptr, VIEWPORT_HEADER_SIZE)
|
|
||||||
header_data = bytes(header_buffer)
|
|
||||||
else:
|
else:
|
||||||
self._shm.seek(0)
|
self._shm.seek(0)
|
||||||
header_data = self._shm.read(VIEWPORT_HEADER_SIZE)
|
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])
|
ready_flag, fmt = struct.unpack('<II', header_data[24:32])
|
||||||
|
|
||||||
if build != VIEWPORT_BUILD:
|
if build != VIEWPORT_BUILD:
|
||||||
# shared memory may have been recreated
|
|
||||||
self._handle_shmem_invalid()
|
self._handle_shmem_invalid()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -538,21 +577,30 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
"""Handle invalid/stale shared memory by cleaning up and allowing re-init."""
|
"""Handle invalid/stale shared memory by cleaning up and allowing re-init."""
|
||||||
self._cleanup_shared_memory()
|
self._cleanup_shared_memory()
|
||||||
self._initialized = False
|
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._texture = None
|
||||||
self._last_frame_id = 0
|
self._last_frame_id = 0
|
||||||
|
|
||||||
def _read_framebuffer(self):
|
def _read_framebuffer(self):
|
||||||
"""Read framebuffer from shared memory."""
|
"""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()
|
header = self._read_header()
|
||||||
if not header:
|
if not header:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if header['frame_id'] == self._last_frame_id:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if header['ready_flag'] == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
width = header['width']
|
width = header['width']
|
||||||
height = header['height']
|
height = header['height']
|
||||||
|
|
||||||
@ -565,15 +613,26 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
if not self._shm_ptr:
|
if not self._shm_ptr:
|
||||||
return None
|
return None
|
||||||
pixel_buffer = (ctypes.c_ubyte * pixel_size)()
|
if HAS_NUMPY:
|
||||||
ctypes.memmove(pixel_buffer, self._shm_ptr + VIEWPORT_HEADER_SIZE, pixel_size)
|
# Single copy: shared memory -> numpy array
|
||||||
pixels = bytes(pixel_buffer)
|
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)
|
ready_bytes = struct.pack('<I', 0)
|
||||||
ctypes.memmove(self._shm_ptr + OFFSET_READY_FLAG, ready_bytes, 4)
|
ctypes.memmove(self._shm_ptr + OFFSET_READY_FLAG, ready_bytes, 4)
|
||||||
else:
|
else:
|
||||||
self._shm.seek(VIEWPORT_HEADER_SIZE)
|
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.seek(OFFSET_READY_FLAG)
|
||||||
self._shm.write(struct.pack('<I', 0))
|
self._shm.write(struct.pack('<I', 0))
|
||||||
|
|
||||||
@ -581,7 +640,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._width = width
|
self._width = width
|
||||||
self._height = height
|
self._height = height
|
||||||
|
|
||||||
return pixels
|
return pixel_array
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._handle_shmem_invalid()
|
self._handle_shmem_invalid()
|
||||||
print(f"Failed to read framebuffer: {e}")
|
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.seek(OFFSET_KROM_CAMERA_DIRTY)
|
||||||
self._shm.write(clear_data)
|
self._shm.write(clear_data)
|
||||||
|
|
||||||
from mathutils import Quaternion, Matrix, Vector
|
|
||||||
|
|
||||||
rv3d = context.space_data.region_3d if context.space_data else None
|
rv3d = context.space_data.region_3d if context.space_data else None
|
||||||
if not rv3d:
|
if not rv3d:
|
||||||
return
|
return
|
||||||
@ -734,6 +791,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._ensure_initialized()
|
self._ensure_initialized()
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
return
|
return
|
||||||
|
if self._input_enabled_state == enabled:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = struct.pack('<I', 1 if enabled else 0)
|
data = struct.pack('<I', 1 if enabled else 0)
|
||||||
@ -744,6 +803,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
else:
|
else:
|
||||||
self._shm.seek(OFFSET_INPUT_ENABLED)
|
self._shm.seek(OFFSET_INPUT_ENABLED)
|
||||||
self._shm.write(data)
|
self._shm.write(data)
|
||||||
|
self._input_enabled_state = enabled
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -793,7 +853,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._shader = gpu.shader.from_builtin(SHADER_IMAGE)
|
self._shader = gpu.shader.from_builtin(SHADER_IMAGE)
|
||||||
|
|
||||||
def _update_texture(self, pixels, width, height):
|
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()
|
self._ensure_initialized()
|
||||||
try:
|
try:
|
||||||
num_pixels = width * height * 4
|
num_pixels = width * height * 4
|
||||||
@ -801,28 +862,46 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
if len(pixels) < num_pixels:
|
if len(pixels) < num_pixels:
|
||||||
return
|
return
|
||||||
|
|
||||||
# clear old texture if dimensions changed
|
if HAS_GPU_TEXTURE:
|
||||||
if hasattr(self, '_tex_width') and hasattr(self, '_tex_height'):
|
if HAS_NUMPY:
|
||||||
if self._tex_width != width or self._tex_height != height:
|
pixels[3::4] = 255
|
||||||
self._texture = None
|
float_array = pixels.astype(np.float32) / 255.0
|
||||||
# TODO: re-investigate LUT for linearization
|
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
|
||||||
if HAS_NUMPY:
|
else:
|
||||||
pixel_array = np.frombuffer(pixels[:num_pixels], dtype=np.uint8).copy()
|
pixel_array = array.array('B', pixels[:num_pixels])
|
||||||
pixel_array[3::4] = 255
|
for i in range(3, len(pixel_array), 4):
|
||||||
float_array = _srgb_to_linear_lut[pixel_array]
|
pixel_array[i] = 255
|
||||||
float_array[3::4] = 1.0
|
float_pixels = [p / 255.0 for p in pixel_array]
|
||||||
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_array)
|
buffer = gpu.types.Buffer('FLOAT', num_pixels, float_pixels)
|
||||||
else:
|
|
||||||
pixel_array = array.array('B', pixels[:num_pixels])
|
self._texture = gpu.types.GPUTexture((width, height), format='SRGB8_A8', data=buffer)
|
||||||
float_pixels = []
|
else:
|
||||||
for i, p in enumerate(pixel_array):
|
# bgl path for Blender < 3.0
|
||||||
if i % 4 == 3: # alpha channel
|
if not hasattr(self, '_gl_texture_buf') or self._gl_texture_buf[0] == 0:
|
||||||
float_pixels.append(1.0)
|
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
|
||||||
else:
|
bgl.glGenTextures(1, self._gl_texture_buf)
|
||||||
float_pixels.append(_srgb_to_linear_lut_list[p])
|
tex_id = self._gl_texture_buf[0]
|
||||||
buffer = gpu.types.Buffer('FLOAT', len(float_pixels), float_pixels)
|
|
||||||
|
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_width = width
|
||||||
self._tex_height = height
|
self._tex_height = height
|
||||||
|
|
||||||
@ -833,9 +912,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
def render(self, depsgraph):
|
def render(self, depsgraph):
|
||||||
"""Disabled render method for final renders (F12)."""
|
"""Disabled render method for final renders (F12)."""
|
||||||
pass
|
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):
|
def view_update(self, context, depsgraph):
|
||||||
"""Called when the scene is updated."""
|
"""Called when the scene is updated."""
|
||||||
|
if state.is_exporting:
|
||||||
|
return
|
||||||
self._ensure_initialized()
|
self._ensure_initialized()
|
||||||
if not self._init_shared_memory(context):
|
if not self._init_shared_memory(context):
|
||||||
return
|
return
|
||||||
@ -848,6 +941,11 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
|
|
||||||
def view_draw(self, context, depsgraph):
|
def view_draw(self, context, depsgraph):
|
||||||
"""Called to draw the viewport."""
|
"""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._ensure_initialized()
|
||||||
self._handle_mode_switch(context)
|
self._handle_mode_switch(context)
|
||||||
|
|
||||||
@ -872,10 +970,23 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._pending_camera_sync = False
|
self._pending_camera_sync = False
|
||||||
self._sync_camera_once(context)
|
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)
|
self._set_rendered_mode(context)
|
||||||
|
|
||||||
# TODO: input forwarding when viewport is active
|
# 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
|
_active_krom_engine = self # legacy single-engine reference
|
||||||
|
|
||||||
if self._viewport_id:
|
if self._viewport_id:
|
||||||
@ -886,23 +997,19 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
|
|
||||||
if not _input_operator_running:
|
if not _input_operator_running:
|
||||||
bpy.app.timers.register(_start_input_capture, first_interval=0.1)
|
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
|
region = context.region
|
||||||
if region and region.width > 0 and region.height > 0:
|
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)
|
self._read_krom_camera(context)
|
||||||
|
|
||||||
pixels = self._read_framebuffer()
|
pixels = self._read_framebuffer()
|
||||||
|
|
||||||
if pixels:
|
if pixels is not None:
|
||||||
self._update_texture(pixels, self._width, self._height)
|
self._update_texture(pixels, self._width, self._height)
|
||||||
|
|
||||||
if not self._texture:
|
if not self._texture:
|
||||||
@ -917,26 +1024,36 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
bgl.glDisable(bgl.GL_BLEND)
|
bgl.glDisable(bgl.GL_BLEND)
|
||||||
|
|
||||||
region = context.region
|
region = context.region
|
||||||
|
dims = (region.width, region.height)
|
||||||
vertices = (
|
if self._batch is None or self._batch_dims != dims:
|
||||||
(0, 0),
|
vertices = (
|
||||||
(region.width, 0),
|
(0, 0),
|
||||||
(region.width, region.height),
|
(region.width, 0),
|
||||||
(0, region.height),
|
(region.width, region.height),
|
||||||
)
|
(0, region.height),
|
||||||
tex_coords = (
|
)
|
||||||
(0, 1), # Flip Y
|
tex_coords = (
|
||||||
(1, 1),
|
(0, 1),
|
||||||
(1, 0),
|
(1, 1),
|
||||||
(0, 0),
|
(1, 0),
|
||||||
)
|
(0, 0),
|
||||||
indices = ((0, 1, 2), (0, 2, 3))
|
)
|
||||||
|
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.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)
|
self._batch.draw(self._shader)
|
||||||
batch.draw(self._shader)
|
|
||||||
|
if not HAS_GPU_TEXTURE:
|
||||||
|
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
||||||
|
|
||||||
if HAS_GPU_STATE:
|
if HAS_GPU_STATE:
|
||||||
gpu.state.blend_set('NONE')
|
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 = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
|
||||||
batch.draw(shader)
|
batch.draw(shader)
|
||||||
|
|
||||||
if HAS_GPU_STATE:
|
if HAS_GPU_STATE:
|
||||||
gpu.state.blend_set('NONE')
|
gpu.state.blend_set('NONE')
|
||||||
else:
|
else:
|
||||||
bgl.glDisable(bgl.GL_BLEND)
|
bgl.glDisable(bgl.GL_BLEND)
|
||||||
|
|
||||||
|
self.tag_redraw()
|
||||||
|
|
||||||
|
|
||||||
class KROM_OT_viewport_input(bpy.types.Operator):
|
class KROM_OT_viewport_input(bpy.types.Operator):
|
||||||
"""Modal operator to capture and forward input to Krom viewport"""
|
"""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):
|
def modal(self, context, event):
|
||||||
global _input_operator_running
|
global _input_operator_running
|
||||||
|
|
||||||
# check if we should stop
|
|
||||||
if event.type == 'ESC':
|
if event.type == 'ESC':
|
||||||
try:
|
try:
|
||||||
if self._engine:
|
if self._engine:
|
||||||
@ -1002,22 +1120,13 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
|||||||
if event.type in {'SCREEN_SET', 'SCREEN_CHANGED'}:
|
if event.type in {'SCREEN_SET', 'SCREEN_CHANGED'}:
|
||||||
return {'PASS_THROUGH'}
|
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'}
|
return {'PASS_THROUGH'}
|
||||||
|
|
||||||
if context.space_data.type == 'VIEW_3D':
|
engine, area, region = result
|
||||||
if context.engine != 'KROM_VIEWPORT':
|
self._engine = engine
|
||||||
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
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self._engine or not self._engine._initialized:
|
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
|
_input_operator_running = False
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
region = context.region
|
# Check if mouse is over a UI region (toolbar, header, panel)
|
||||||
if not region:
|
mouse_x_abs = event.mouse_x
|
||||||
return {'PASS_THROUGH'}
|
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':
|
# Compute mouse position relative to the WINDOW region
|
||||||
return {'PASS_THROUGH'}
|
mouse_x = mouse_x_abs - region.x
|
||||||
|
mouse_y = region.height - (mouse_y_abs - region.y)
|
||||||
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
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if event.type == 'MOUSEMOVE':
|
if event.type == 'MOUSEMOVE':
|
||||||
@ -1128,49 +1219,63 @@ class KROM_OT_viewport_input(bpy.types.Operator):
|
|||||||
}
|
}
|
||||||
return key_map.get(blender_key)
|
return key_map.get(blender_key)
|
||||||
|
|
||||||
def _get_engine_for_area(self, context, event=None):
|
def _find_viewport_under_mouse(self, event):
|
||||||
"""Get the correct engine for the viewport under the mouse cursor."""
|
"""Find the area, WINDOW region, and engine under the mouse cursor.
|
||||||
# Use mouse coordinates to find which viewport the mouse is over
|
Returns (engine, area, region) or None."""
|
||||||
if event:
|
mouse_x = event.mouse_x
|
||||||
mouse_x = event.mouse_x
|
mouse_y = event.mouse_y
|
||||||
mouse_y = event.mouse_y
|
|
||||||
|
|
||||||
for window in bpy.context.window_manager.windows:
|
for window in bpy.context.window_manager.windows:
|
||||||
for area in window.screen.areas:
|
for area in window.screen.areas:
|
||||||
if area.type != 'VIEW_3D':
|
if area.type != 'VIEW_3D':
|
||||||
continue
|
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):
|
area.y <= mouse_y < area.y + area.height):
|
||||||
|
continue
|
||||||
|
|
||||||
for space in area.spaces:
|
# Find the WINDOW region for this area
|
||||||
if space.type == 'VIEW_3D':
|
win_region = None
|
||||||
try:
|
for rgn in area.regions:
|
||||||
viewport_id = hex(space.as_pointer())[-6:]
|
if rgn.type == 'WINDOW':
|
||||||
if viewport_id in _active_krom_engines:
|
win_region = rgn
|
||||||
return _active_krom_engines[viewport_id]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if context.space_data:
|
if not win_region:
|
||||||
try:
|
continue
|
||||||
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 _active_viewport_id and _active_viewport_id in _active_krom_engines:
|
# Check if mouse is actually within the WINDOW region bounds
|
||||||
return _active_krom_engines[_active_viewport_id]
|
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):
|
def invoke(self, context, event):
|
||||||
self._engine = self._get_engine_for_area(context)
|
result = self._find_viewport_under_mouse(event)
|
||||||
if not self._engine:
|
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'}
|
return {'CANCELLED'}
|
||||||
|
self._engine = result[0]
|
||||||
context.window_manager.modal_handler_add(self)
|
context.window_manager.modal_handler_add(self)
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
@ -1192,8 +1297,12 @@ def _start_input_capture():
|
|||||||
for region in area.regions:
|
for region in area.regions:
|
||||||
if region.type == 'WINDOW':
|
if region.type == 'WINDOW':
|
||||||
override = {'window': window, 'area': area, 'region': region}
|
override = {'window': window, 'area': area, 'region': region}
|
||||||
with bpy.context.temp_override(**override):
|
if bpy.app.version >= (3, 0, 0):
|
||||||
bpy.ops.krom.viewport_input('INVOKE_DEFAULT')
|
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
|
_input_operator_running = True
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -1208,7 +1317,9 @@ def get_panels():
|
|||||||
'VIEWLAYER_PT_layer_passes',
|
'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 = []
|
panels = []
|
||||||
for panel in bpy.types.Panel.__subclasses__():
|
for panel in bpy.types.Panel.__subclasses__():
|
||||||
@ -1262,6 +1373,12 @@ class KROM_OT_stop_viewport(bpy.types.Operator):
|
|||||||
make.stop_viewport(viewport_id)
|
make.stop_viewport(viewport_id)
|
||||||
global _active_krom_engines
|
global _active_krom_engines
|
||||||
if viewport_id in _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]
|
del _active_krom_engines[viewport_id]
|
||||||
if context.space_data and hasattr(context.space_data, 'shading'):
|
if context.space_data and hasattr(context.space_data, 'shading'):
|
||||||
context.space_data.shading.type = 'SOLID'
|
context.space_data.shading.type = 'SOLID'
|
||||||
|
|||||||
Reference in New Issue
Block a user