forked from LeenkxTeam/LNXSDK
Merge pull request 'main' (#131) from Onek8/LNXSDK:main into main
Reviewed-on: LeenkxTeam/LNXSDK#131
This commit is contained in:
BIN
Krom/Krom.exe
BIN
Krom/Krom.exe
Binary file not shown.
@ -136,7 +136,10 @@ def always() -> float:
|
|||||||
# Force ui redraw
|
# Force ui redraw
|
||||||
if state.redraw_ui:
|
if state.redraw_ui:
|
||||||
if bpy.context.screen is not None:
|
if bpy.context.screen is not None:
|
||||||
|
krom_active = bool(lnx.render_engine._active_krom_engines)
|
||||||
for area in bpy.context.screen.areas:
|
for area in bpy.context.screen.areas:
|
||||||
|
if area.type == 'VIEW_3D' and krom_active:
|
||||||
|
continue
|
||||||
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
|
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
|
||||||
area.tag_redraw()
|
area.tag_redraw()
|
||||||
state.redraw_ui = False
|
state.redraw_ui = False
|
||||||
|
|||||||
@ -804,41 +804,71 @@ def viewport_build_done():
|
|||||||
_viewport_pending_launches.clear()
|
_viewport_pending_launches.clear()
|
||||||
log.error('Viewport build failed, check console')
|
log.error('Viewport build failed, check console')
|
||||||
|
|
||||||
def play_viewport(viewport_id, width=1920, height=1080):
|
def play(viewport_id=None, width=1920, height=1080):
|
||||||
"""Launch Krom in a viewport"""
|
"""Launch player external or embedded when viewport_id is passed"""
|
||||||
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 viewport_id and 'Lnx' not in bpy.data.worlds:
|
||||||
log.error('No viewport_id: play_viewport requires an id')
|
|
||||||
return
|
|
||||||
|
|
||||||
if 'Lnx' not in bpy.data.worlds:
|
|
||||||
log.error('No Lnx world found - cannot start viewport server')
|
log.error('No Lnx world found - cannot start viewport server')
|
||||||
return
|
return
|
||||||
|
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
|
target = 'krom' if viewport_id else runtime_to_target()
|
||||||
|
|
||||||
krom_js_path = lnx.utils.get_fp_build() + '/debug/krom/krom.js'
|
khajs_path = get_khajs_path(target)
|
||||||
if os.path.exists(krom_js_path) and not wrd.lnx_recompile:
|
if not wrd.lnx_cache_build or \
|
||||||
run_viewport_runtime(viewport_id, width, height)
|
not os.path.isfile(khajs_path) or \
|
||||||
return
|
assets.khafile_defs_last != assets.khafile_defs or \
|
||||||
|
state.last_target != state.target:
|
||||||
|
wrd.lnx_recompile = True
|
||||||
|
|
||||||
pending_entry = (viewport_id, width, height)
|
state.last_target = state.target
|
||||||
if pending_entry not in _viewport_pending_launches:
|
|
||||||
_viewport_pending_launches.append(pending_entry)
|
|
||||||
|
|
||||||
if _viewport_build_in_progress:
|
state.mod_scripts = []
|
||||||
if _viewport_proc_build is not None and _viewport_proc_build.poll() is None:
|
script_path = lnx.utils.get_fp() + '/Sources/' + lnx.utils.safestr(wrd.lnx_project_package)
|
||||||
log.info(f'Build in progress, viewport {viewport_id} will launch when ready')
|
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]
|
||||||
|
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()
|
||||||
|
|
||||||
|
if viewport_id:
|
||||||
|
krom_js_path = lnx.utils.get_fp_build() + '/debug/krom/krom.js'
|
||||||
|
if os.path.exists(krom_js_path) and not wrd.lnx_recompile:
|
||||||
|
run_viewport_runtime(viewport_id, width, height)
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
_viewport_build_in_progress = False
|
|
||||||
|
|
||||||
_viewport_build_in_progress = True
|
pending_entry = (viewport_id, width, height)
|
||||||
|
if pending_entry not in _viewport_pending_launches:
|
||||||
|
_viewport_pending_launches.append(pending_entry)
|
||||||
|
|
||||||
build(target='krom', is_viewport=True, viewport_width=width, viewport_height=height)
|
if _viewport_build_in_progress:
|
||||||
|
if _viewport_proc_build is not None and _viewport_proc_build.poll() is None:
|
||||||
|
log.info(f'Build in progress, viewport {viewport_id} will launch when ready')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
_viewport_build_in_progress = False
|
||||||
|
|
||||||
compile_viewport(assets_only=(not wrd.lnx_recompile))
|
_viewport_build_in_progress = True
|
||||||
|
|
||||||
|
assets.invalidate_enabled = False
|
||||||
|
build(target='krom', is_viewport=True, viewport_width=width, viewport_height=height)
|
||||||
|
compile_viewport(assets_only=(not wrd.lnx_recompile))
|
||||||
|
assets.invalidate_enabled = True
|
||||||
|
else:
|
||||||
|
build(target=target, is_play=True)
|
||||||
|
compile(assets_only=(not wrd.lnx_recompile))
|
||||||
|
|
||||||
|
|
||||||
def stop_viewport(viewport_id=None):
|
def stop_viewport(viewport_id=None):
|
||||||
@ -905,43 +935,6 @@ def get_khajs_path(target):
|
|||||||
return lnx.utils.build_dir() + '/debug/krom/krom.js'
|
return lnx.utils.build_dir() + '/debug/krom/krom.js'
|
||||||
return lnx.utils.build_dir() + '/debug/html5/kha.js'
|
return lnx.utils.build_dir() + '/debug/html5/kha.js'
|
||||||
|
|
||||||
def play():
|
|
||||||
global scripts_mtime
|
|
||||||
wrd = bpy.data.worlds['Lnx']
|
|
||||||
|
|
||||||
build(target=runtime_to_target(), is_play=True)
|
|
||||||
|
|
||||||
khajs_path = get_khajs_path(state.target)
|
|
||||||
if not wrd.lnx_cache_build or \
|
|
||||||
not os.path.isfile(khajs_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) # Trait props
|
|
||||||
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: # Trait props
|
|
||||||
lnx.utils.fetch_trait_props()
|
|
||||||
|
|
||||||
compile(assets_only=(not wrd.lnx_recompile))
|
|
||||||
|
|
||||||
def build_success():
|
def build_success():
|
||||||
log.clear()
|
log.clear()
|
||||||
wrd = bpy.data.worlds['Lnx']
|
wrd = bpy.data.worlds['Lnx']
|
||||||
|
|||||||
@ -11,12 +11,16 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import array
|
import array
|
||||||
import atexit
|
import atexit
|
||||||
|
import time
|
||||||
from mathutils import Quaternion, Matrix, Vector
|
from mathutils import Quaternion, Matrix, Vector
|
||||||
import lnx.make_state as state
|
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:
|
try:
|
||||||
import bgl
|
import bgl
|
||||||
|
HAS_BGL = callable(getattr(bgl, 'glGenTextures', None))
|
||||||
|
except ImportError:
|
||||||
|
HAS_BGL = False
|
||||||
SHADER_UNIFORM_COLOR = 'UNIFORM_COLOR' if bpy.app.version >= (3, 4, 0) else '2D_UNIFORM_COLOR'
|
SHADER_UNIFORM_COLOR = 'UNIFORM_COLOR' if bpy.app.version >= (3, 4, 0) else '2D_UNIFORM_COLOR'
|
||||||
SHADER_IMAGE = 'IMAGE' if bpy.app.version >= (3, 4, 0) else '2D_IMAGE'
|
SHADER_IMAGE = 'IMAGE' if bpy.app.version >= (3, 4, 0) else '2D_IMAGE'
|
||||||
HAS_GPU_TEXTURE = bpy.app.version >= (3, 0, 0)
|
HAS_GPU_TEXTURE = bpy.app.version >= (3, 0, 0)
|
||||||
@ -124,6 +128,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._last_frame_id = 0
|
self._last_frame_id = 0
|
||||||
self._width = 0
|
self._width = 0
|
||||||
self._height = 0
|
self._height = 0
|
||||||
|
self._tex_width = 0
|
||||||
|
self._tex_height = 0
|
||||||
self._initialized = False
|
self._initialized = False
|
||||||
self._shader = None
|
self._shader = None
|
||||||
self._batch = None
|
self._batch = None
|
||||||
@ -140,6 +146,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._last_region_dims = None
|
self._last_region_dims = None
|
||||||
self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
|
self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
|
||||||
self._engine_id = id(self)
|
self._engine_id = id(self)
|
||||||
|
self._viewport_image = None
|
||||||
|
self._float_buffer = None
|
||||||
# print(f"New engine instance created: {self._engine_id}")
|
# print(f"New engine instance created: {self._engine_id}")
|
||||||
|
|
||||||
def _restore_overlay(self, context=None):
|
def _restore_overlay(self, context=None):
|
||||||
@ -247,7 +255,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
def deferred_launch():
|
def deferred_launch():
|
||||||
try:
|
try:
|
||||||
import lnx.make as make
|
import lnx.make as make
|
||||||
make.play_viewport(viewport_id, width, height)
|
make.play(viewport_id, width, height)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to launch viewport: {e}")
|
print(f"Failed to launch viewport: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
@ -498,7 +506,7 @@ 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:
|
if HAS_BGL and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
|
||||||
try:
|
try:
|
||||||
bgl.glDeleteTextures(1, self._gl_texture_buf)
|
bgl.glDeleteTextures(1, self._gl_texture_buf)
|
||||||
except:
|
except:
|
||||||
@ -783,21 +791,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
if len(pixels) < num_pixels:
|
if len(pixels) < num_pixels:
|
||||||
return
|
return
|
||||||
|
|
||||||
if HAS_GPU_TEXTURE:
|
if HAS_BGL:
|
||||||
if HAS_NUMPY:
|
# Fast path: bgl with GL_UNSIGNED_BYTE, no float conversion, in-place update
|
||||||
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:
|
if not hasattr(self, '_gl_texture_buf') or self._gl_texture_buf[0] == 0:
|
||||||
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
|
self._gl_texture_buf = bgl.Buffer(bgl.GL_INT, 1)
|
||||||
bgl.glGenTextures(1, self._gl_texture_buf)
|
bgl.glGenTextures(1, self._gl_texture_buf)
|
||||||
@ -817,12 +812,43 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array))
|
pixel_buffer = bgl.Buffer(bgl.GL_UNSIGNED_BYTE, num_pixels, bytes(pixel_array))
|
||||||
|
|
||||||
internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8)
|
internal_format = getattr(bgl, 'GL_SRGB8_ALPHA8', bgl.GL_RGBA8)
|
||||||
bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, internal_format, width, height, 0,
|
if self._tex_width == width and self._tex_height == height:
|
||||||
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer)
|
bgl.glTexSubImage2D(bgl.GL_TEXTURE_2D, 0, 0, 0, width, height,
|
||||||
|
bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, pixel_buffer)
|
||||||
|
else:
|
||||||
|
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)
|
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
||||||
|
|
||||||
self._texture = tex_id
|
self._texture = tex_id
|
||||||
|
elif HAS_GPU_TEXTURE:
|
||||||
|
# Blender 4.x+ path: use bpy.Image + gpu.texture.from_image
|
||||||
|
# from_image returns a cached GPUTexture (no 20ms constructor)
|
||||||
|
# pixels updated via foreach_set (fast C-level copy) + update()
|
||||||
|
if self._viewport_image is None or self._viewport_image.size[0] != width or self._viewport_image.size[1] != height:
|
||||||
|
if self._viewport_image is not None:
|
||||||
|
bpy.data.images.remove(self._viewport_image)
|
||||||
|
self._viewport_image = bpy.data.images.new(
|
||||||
|
"_lnx_viewport", width, height, float_buffer=False)
|
||||||
|
self._float_buffer = None
|
||||||
|
|
||||||
|
if HAS_NUMPY:
|
||||||
|
pixels[3::4] = 255
|
||||||
|
if self._float_buffer is None or len(self._float_buffer) != num_pixels:
|
||||||
|
self._float_buffer = np.empty(num_pixels, dtype=np.float32)
|
||||||
|
self._float_buffer[:num_pixels] = pixels[:num_pixels]
|
||||||
|
self._float_buffer *= (1.0 / 255.0)
|
||||||
|
self._viewport_image.pixels.foreach_set(self._float_buffer)
|
||||||
|
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]
|
||||||
|
self._viewport_image.pixels.foreach_set(float_pixels)
|
||||||
|
|
||||||
|
self._viewport_image.update()
|
||||||
|
self._texture = gpu.texture.from_image(self._viewport_image)
|
||||||
|
|
||||||
self._tex_width = width
|
self._tex_width = width
|
||||||
self._tex_height = height
|
self._tex_height = height
|
||||||
|
|
||||||
@ -838,11 +864,17 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
"""Called by Blender when the engine instance is destroyed."""
|
"""Called by Blender when the engine instance is destroyed."""
|
||||||
self._stop_krom_process()
|
self._stop_krom_process()
|
||||||
self._restore_overlay()
|
self._restore_overlay()
|
||||||
if not HAS_GPU_TEXTURE and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
|
if HAS_BGL and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
|
||||||
try:
|
try:
|
||||||
bgl.glDeleteTextures(1, self._gl_texture_buf)
|
bgl.glDeleteTextures(1, self._gl_texture_buf)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
if self._viewport_image is not None:
|
||||||
|
try:
|
||||||
|
bpy.data.images.remove(self._viewport_image)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self._viewport_image = None
|
||||||
if self._viewport_id and self._viewport_id in _active_krom_engines:
|
if self._viewport_id and self._viewport_id in _active_krom_engines:
|
||||||
del _active_krom_engines[self._viewport_id]
|
del _active_krom_engines[self._viewport_id]
|
||||||
|
|
||||||
@ -928,6 +960,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._last_region_dims = dims
|
self._last_region_dims = dims
|
||||||
self._request_resize(region.width, region.height)
|
self._request_resize(region.width, region.height)
|
||||||
|
|
||||||
|
self._write_camera_matrices(context)
|
||||||
self._read_krom_camera(context)
|
self._read_krom_camera(context)
|
||||||
|
|
||||||
pixels = self._read_framebuffer()
|
pixels = self._read_framebuffer()
|
||||||
@ -966,7 +999,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
self._batch_dims = dims
|
self._batch_dims = dims
|
||||||
|
|
||||||
self._shader.bind()
|
self._shader.bind()
|
||||||
if HAS_GPU_TEXTURE:
|
_is_gl_texture = isinstance(self._texture, int)
|
||||||
|
if not _is_gl_texture:
|
||||||
self._shader.uniform_sampler("image", self._texture)
|
self._shader.uniform_sampler("image", self._texture)
|
||||||
else:
|
else:
|
||||||
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
||||||
@ -975,7 +1009,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
|
|||||||
|
|
||||||
self._batch.draw(self._shader)
|
self._batch.draw(self._shader)
|
||||||
|
|
||||||
if not HAS_GPU_TEXTURE:
|
if _is_gl_texture:
|
||||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
|
||||||
|
|
||||||
if HAS_GPU_STATE:
|
if HAS_GPU_STATE:
|
||||||
|
|||||||
Reference in New Issue
Block a user