Finished Viewport

This commit is contained in:
2026-07-23 23:02:14 -07:00
parent 2be36398f7
commit 78452aaf67
4 changed files with 113 additions and 83 deletions

View File

@ -11,12 +11,16 @@ import sys
import os
import array
import atexit
import time
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:
try:
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_IMAGE = 'IMAGE' if bpy.app.version >= (3, 4, 0) else '2D_IMAGE'
HAS_GPU_TEXTURE = bpy.app.version >= (3, 0, 0)
@ -124,6 +128,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_frame_id = 0
self._width = 0
self._height = 0
self._tex_width = 0
self._tex_height = 0
self._initialized = False
self._shader = None
self._batch = None
@ -140,6 +146,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_region_dims = None
self._header_buffer = (ctypes.c_ubyte * VIEWPORT_HEADER_SIZE)()
self._engine_id = id(self)
self._viewport_image = None
self._float_buffer = None
# print(f"New engine instance created: {self._engine_id}")
def _restore_overlay(self, context=None):
@ -247,7 +255,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
def deferred_launch():
try:
import lnx.make as make
make.play_viewport(viewport_id, width, height)
make.play(viewport_id, width, height)
except Exception as e:
print(f"Failed to launch viewport: {e}")
import traceback
@ -498,7 +506,7 @@ 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:
if HAS_BGL and hasattr(self, '_gl_texture_buf') and self._gl_texture_buf[0] != 0:
try:
bgl.glDeleteTextures(1, self._gl_texture_buf)
except:
@ -783,21 +791,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
if len(pixels) < num_pixels:
return
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 HAS_BGL:
# Fast path: bgl with GL_UNSIGNED_BYTE, no float conversion, in-place update
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)
@ -817,12 +812,43 @@ class KromViewportEngine(bpy.types.RenderEngine):
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)
if self._tex_width == width and self._tex_height == height:
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)
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_height = height
@ -838,11 +864,17 @@ class KromViewportEngine(bpy.types.RenderEngine):
"""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:
if HAS_BGL 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_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:
del _active_krom_engines[self._viewport_id]
@ -928,6 +960,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._last_region_dims = dims
self._request_resize(region.width, region.height)
self._write_camera_matrices(context)
self._read_krom_camera(context)
pixels = self._read_framebuffer()
@ -966,7 +999,8 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._batch_dims = dims
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)
else:
bgl.glActiveTexture(bgl.GL_TEXTURE0)
@ -975,7 +1009,7 @@ class KromViewportEngine(bpy.types.RenderEngine):
self._batch.draw(self._shader)
if not HAS_GPU_TEXTURE:
if _is_gl_texture:
bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0)
if HAS_GPU_STATE: