luvatrix 0.2.4__tar.gz → 0.2.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. {luvatrix-0.2.4/luvatrix.egg-info → luvatrix-0.2.6}/PKG-INFO +1 -1
  2. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix/app.py +62 -19
  3. {luvatrix-0.2.4 → luvatrix-0.2.6/luvatrix.egg-info}/PKG-INFO +1 -1
  4. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix.egg-info/SOURCES.txt +14 -0
  5. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/__init__.py +1 -1
  6. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/accel.py +8 -4
  7. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/display_runtime.py +26 -2
  8. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/unified_runtime.py +38 -9
  9. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/window_matrix.py +99 -14
  10. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/android/runner.py +20 -5
  11. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/android/scene_target.py +81 -9
  12. luvatrix-0.2.6/luvatrix_core/platform/android/vulkan_target.py +93 -0
  13. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/targets/base.py +3 -0
  14. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/build.gradle.kts +18 -0
  15. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/proguard-rules.pro +4 -0
  16. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/AndroidManifest.xml +1 -0
  17. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/cpp/CMakeLists.txt +4 -0
  18. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_primitive_batch.frag +8 -0
  19. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_primitive_batch.vert +23 -0
  20. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_primitive_batch_shaders.h +249 -0
  21. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_vulkan_renderer.cpp +837 -44
  22. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/CameraBridge.kt +4 -1
  23. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LatestFrameMailbox.kt +23 -0
  24. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LaunchPerformance.kt +73 -0
  25. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LuvatrixApplication.kt +82 -0
  26. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LuvatrixVulkanView.kt +378 -79
  27. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/MainActivity.kt +6 -16
  28. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/NativeVulkan.kt +17 -0
  29. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/StartupResources.kt +215 -0
  30. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/python/luvatrix_android_boot.py +61 -25
  31. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/release/generated/baselineProfiles/baseline-prof.txt +877 -0
  32. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/release/generated/baselineProfiles/startup-prof.txt +877 -0
  33. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/test/java/com/luvatrix/app/BootstrapStateTest.kt +93 -0
  34. luvatrix-0.2.6/luvatrix_core/templates/native/android/app/src/test/java/com/luvatrix/app/LaunchTimelineTest.kt +94 -0
  35. luvatrix-0.2.6/luvatrix_core/templates/native/android/benchmark/build.gradle.kts +41 -0
  36. luvatrix-0.2.6/luvatrix_core/templates/native/android/benchmark/src/main/java/com/luvatrix/benchmark/BaselineProfileGenerator.kt +23 -0
  37. luvatrix-0.2.6/luvatrix_core/templates/native/android/benchmark/src/main/java/com/luvatrix/benchmark/StartupBenchmark.kt +40 -0
  38. luvatrix-0.2.6/luvatrix_core/templates/native/android/benchmark/src/main/java/com/luvatrix/benchmark/StartupJourney.kt +16 -0
  39. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/build.gradle.kts +2 -0
  40. luvatrix-0.2.6/luvatrix_core/templates/native/android/gradle.properties +2 -0
  41. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/settings.gradle.kts +1 -0
  42. luvatrix-0.2.6/luvatrix_ui/__init__.py +122 -0
  43. luvatrix-0.2.6/luvatrix_ui/text/__init__.py +38 -0
  44. {luvatrix-0.2.4 → luvatrix-0.2.6}/pyproject.toml +1 -1
  45. luvatrix-0.2.4/luvatrix_core/platform/android/vulkan_target.py +0 -50
  46. luvatrix-0.2.4/luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LaunchPerformance.kt +0 -38
  47. luvatrix-0.2.4/luvatrix_core/templates/native/android/app/src/test/java/com/luvatrix/app/LaunchTimelineTest.kt +0 -40
  48. luvatrix-0.2.4/luvatrix_core/templates/native/android/gradle.properties +0 -1
  49. luvatrix-0.2.4/luvatrix_ui/__init__.py +0 -205
  50. luvatrix-0.2.4/luvatrix_ui/text/__init__.py +0 -32
  51. {luvatrix-0.2.4 → luvatrix-0.2.6}/LICENSE +0 -0
  52. {luvatrix-0.2.4 → luvatrix-0.2.6}/MANIFEST.in +0 -0
  53. {luvatrix-0.2.4 → luvatrix-0.2.6}/README.md +0 -0
  54. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix/__init__.py +0 -0
  55. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix/auth/__init__.py +0 -0
  56. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix/auth/calendar.py +0 -0
  57. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix/auth/google.py +0 -0
  58. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix/auth/sign_in.py +0 -0
  59. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix/auth/ui.py +0 -0
  60. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix.egg-info/dependency_links.txt +0 -0
  61. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix.egg-info/entry_points.txt +0 -0
  62. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix.egg-info/requires.txt +0 -0
  63. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix.egg-info/top_level.txt +0 -0
  64. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/_accel_native.c +0 -0
  65. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/_accel_native.pyx +0 -0
  66. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/__init__.py +0 -0
  67. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/app_runtime.py +0 -0
  68. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/audit.py +0 -0
  69. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/coordinates.py +0 -0
  70. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/debug_capture.py +0 -0
  71. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/debug_menu.py +0 -0
  72. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/energy_safety.py +0 -0
  73. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/engine.py +0 -0
  74. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/events.py +0 -0
  75. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/frame_rate_controller.py +0 -0
  76. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/hdi_thread.py +0 -0
  77. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/interaction_work.py +0 -0
  78. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/process_runtime.py +0 -0
  79. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/process_sdk.py +0 -0
  80. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/protocol_governance.py +0 -0
  81. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/scene_display_runtime.py +0 -0
  82. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/scene_graph.py +0 -0
  83. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/scene_rasterizer.py +0 -0
  84. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/sensor_manager.py +0 -0
  85. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/core/ui_frame_renderer.py +0 -0
  86. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/perf/copy_telemetry.py +0 -0
  87. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/__init__.py +0 -0
  88. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/android/__init__.py +0 -0
  89. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/android/camera_processing_contract.py +0 -0
  90. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/android/camera_style.py +0 -0
  91. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/android/hdi_source.py +0 -0
  92. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/android/sensors.py +0 -0
  93. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/frame_pipeline.py +0 -0
  94. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/ios/__init__.py +0 -0
  95. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/ios/hdi_source.py +0 -0
  96. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/ios/lifecycle.py +0 -0
  97. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/ios/metal_backend.py +0 -0
  98. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/ios/runner.py +0 -0
  99. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/ios/scene_target.py +0 -0
  100. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/ios/window_system.py +0 -0
  101. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/__init__.py +0 -0
  102. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/hdi_source.py +0 -0
  103. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/iohid_source.py +0 -0
  104. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/metal_backend.py +0 -0
  105. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/metal_presenter.py +0 -0
  106. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/metal_scene_target.py +0 -0
  107. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/sensors.py +0 -0
  108. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/vulkan_backend.py +0 -0
  109. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/vulkan_presenter.py +0 -0
  110. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/macos/window_system.py +0 -0
  111. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/package_sync.py +0 -0
  112. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/vulkan_compat.py +0 -0
  113. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/vulkan_scaling.py +0 -0
  114. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/vulkan_setup.py +0 -0
  115. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/__init__.py +0 -0
  116. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/build.py +0 -0
  117. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/command_buffer.py +0 -0
  118. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/python_shim/luvatrix/__init__.py +0 -0
  119. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/python_shim/luvatrix/app.py +0 -0
  120. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/runtime_assets/index.html +0 -0
  121. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/runtime_assets/runtime/command-buffer.js +0 -0
  122. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/runtime_assets/runtime/input.js +0 -0
  123. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/runtime_assets/runtime/luvatrix-web.js +0 -0
  124. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/runtime_assets/runtime/package.json +0 -0
  125. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/runtime_assets/runtime/renderers.js +0 -0
  126. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/runtime_assets/runtime/spotify-bridge.js +0 -0
  127. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/server.py +0 -0
  128. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/platform/web/websocket_target.py +0 -0
  129. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/render/__init__.py +0 -0
  130. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/render/framebuffer.py +0 -0
  131. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/render/svg.py +0 -0
  132. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/scaffold.py +0 -0
  133. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/targets/__init__.py +0 -0
  134. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/targets/cpu_scene_target.py +0 -0
  135. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/targets/metal_target.py +0 -0
  136. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/targets/scene_target.py +0 -0
  137. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/targets/vulkan_target.py +0 -0
  138. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/targets/web_target.py +0 -0
  139. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/.gitignore +0 -0
  140. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/assets/luvatrix_bitmap_font.txt +0 -0
  141. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/assets/luvatrix_launch_config.json +0 -0
  142. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/assets/luvatrix_matrix_font_alpha.txt +0 -0
  143. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_camera_preview.frag +0 -0
  144. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_camera_preview_shaders.h +0 -0
  145. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/python/.gitignore +0 -0
  146. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/python/luvatrix_launch_config.json +0 -0
  147. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  148. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  149. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  150. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  151. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  152. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/app/src/main/res/values/styles.xml +0 -0
  153. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/gradlew +0 -0
  154. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/scripts/emulator_acceptance.sh +0 -0
  155. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/android/scripts/sync_python_assets.sh +0 -0
  156. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/AppDelegate.swift +0 -0
  157. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  158. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/Assets.xcassets/AppIcon.appiconset/icon.png +0 -0
  159. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/Assets.xcassets/Contents.json +0 -0
  160. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/Info.plist +0 -0
  161. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/Luvatrix-Bridging-Header.h +0 -0
  162. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/Luvatrix.entitlements +0 -0
  163. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/PythonBridge.h +0 -0
  164. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/PythonBridge.m +0 -0
  165. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/Luvatrix/main.swift +0 -0
  166. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/project.yml +0 -0
  167. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/templates/native/ios/scripts/setup_ios.sh +0 -0
  168. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/ui/__init__.py +0 -0
  169. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/ui/element.py +0 -0
  170. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_core/ui/page_loader.py +0 -0
  171. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/__init__.py +0 -0
  172. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/adapters/__init__.py +0 -0
  173. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/adapters/normalize.py +0 -0
  174. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/api.py +0 -0
  175. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/compile/__init__.py +0 -0
  176. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/compile/app_protocol.py +0 -0
  177. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/display.py +0 -0
  178. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/dynamic_axis.py +0 -0
  179. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/errors.py +0 -0
  180. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/figure.py +0 -0
  181. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/live.py +0 -0
  182. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/raster/__init__.py +0 -0
  183. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/raster/canvas.py +0 -0
  184. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/raster/draw_lines.py +0 -0
  185. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/raster/draw_markers.py +0 -0
  186. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/raster/draw_text.py +0 -0
  187. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/raster/layers.py +0 -0
  188. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/scales.py +0 -0
  189. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_plot/series.py +0 -0
  190. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/component_schema.py +0 -0
  191. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/controls/__init__.py +0 -0
  192. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/controls/button.py +0 -0
  193. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/controls/interaction.py +0 -0
  194. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/controls/stained_glass_button.py +0 -0
  195. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/controls/svg_component.py +0 -0
  196. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/controls/svg_renderer.py +0 -0
  197. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planes_protocol.py +0 -0
  198. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planes_runtime.py +0 -0
  199. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planes_v2_validator.py +0 -0
  200. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planning/__init__.py +0 -0
  201. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planning/agile_renderer.py +0 -0
  202. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planning/exporters.py +0 -0
  203. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planning/gantt_renderer.py +0 -0
  204. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planning/interaction.py +0 -0
  205. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planning/schema.py +0 -0
  206. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/planning/validation.py +0 -0
  207. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/style/__init__.py +0 -0
  208. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/style/theme.py +0 -0
  209. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/table/__init__.py +0 -0
  210. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/table/component.py +0 -0
  211. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/text/component.py +0 -0
  212. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/text/renderer.py +0 -0
  213. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/text/wrapping.py +0 -0
  214. {luvatrix-0.2.4 → luvatrix-0.2.6}/luvatrix_ui/ui_ir.py +0 -0
  215. {luvatrix-0.2.4 → luvatrix-0.2.6}/main.py +0 -0
  216. {luvatrix-0.2.4 → luvatrix-0.2.6}/setup.cfg +0 -0
  217. {luvatrix-0.2.4 → luvatrix-0.2.6}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: luvatrix
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: Luvatrix runtime and plotting toolkit
5
5
  Author: Luvatrix contributors
6
6
  License-Expression: LicenseRef-Proprietary
@@ -3,31 +3,33 @@ from __future__ import annotations
3
3
  from collections.abc import Callable
4
4
  from contextlib import AbstractContextManager
5
5
  from dataclasses import dataclass, field
6
- from importlib import resources
6
+ from importlib import import_module, resources
7
7
  import importlib.util
8
8
  import math
9
9
  import platform
10
10
  from pathlib import Path
11
- from typing import Any
12
-
13
- from luvatrix_core.core.app_runtime import (
14
- APP_PROTOCOL_VERSION,
15
- AppContext,
16
- AppLifecycle,
17
- AppManifest,
18
- AppRuntime,
19
- AppUIRenderer,
20
- AppVariant,
21
- AppWebConfig,
22
- ResolvedAppVariant,
23
- )
24
- from luvatrix_core.core.hdi_thread import HDIEvent, HDIThread
25
- from luvatrix_core.core.interaction_work import InteractionAwareWorkScheduler
26
- from luvatrix_core.core.sensor_manager import SensorManagerThread, SensorSample
27
- from luvatrix_core.core.window_matrix import FullRewrite, WriteBatch, WindowMatrix
28
- from luvatrix_core import accel
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from luvatrix_core.core.protocol_governance import CURRENT_PROTOCOL_VERSION
29
14
  from luvatrix_ui.text.wrapping import TextWrapping, layout_text, prepare_text
30
15
 
16
+ if TYPE_CHECKING:
17
+ from luvatrix_core.core.app_runtime import (
18
+ AppContext,
19
+ AppLifecycle,
20
+ AppManifest,
21
+ AppRuntime,
22
+ AppUIRenderer,
23
+ AppVariant,
24
+ AppWebConfig,
25
+ ResolvedAppVariant,
26
+ )
27
+ from luvatrix_core.core.hdi_thread import HDIEvent
28
+ from luvatrix_core.core.sensor_manager import SensorSample
29
+
30
+
31
+ APP_PROTOCOL_VERSION = CURRENT_PROTOCOL_VERSION
32
+
31
33
  PLATFORM_MACOS = "macos"
32
34
  PLATFORM_IOS = "ios"
33
35
  PLATFORM_ANDROID = "android"
@@ -1353,6 +1355,8 @@ def draw_rounded_rect_to_matrix(
1353
1355
  antialias: bool = True,
1354
1356
  ):
1355
1357
  """Source-over rasterize a rounded rectangle into an RGBA matrix."""
1358
+ from luvatrix_core import accel
1359
+
1356
1360
  width = int(width)
1357
1361
  height = int(height)
1358
1362
  if width <= 0 or height <= 0:
@@ -1508,6 +1512,8 @@ class MatrixFrame(AbstractContextManager["MatrixFrame"]):
1508
1512
  self._frame = None
1509
1513
 
1510
1514
  def __enter__(self) -> "MatrixFrame":
1515
+ from luvatrix_core import accel
1516
+
1511
1517
  snap = self._ctx.read_matrix_snapshot()
1512
1518
  h, w, _ = snap.shape
1513
1519
  self._frame = accel.zeros((h, w, 4))
@@ -1516,6 +1522,8 @@ class MatrixFrame(AbstractContextManager["MatrixFrame"]):
1516
1522
 
1517
1523
  def __exit__(self, exc_type: object, exc: object, tb: object) -> bool:
1518
1524
  if exc_type is None and self._frame is not None:
1525
+ from luvatrix_core.core.window_matrix import FullRewrite, WriteBatch
1526
+
1519
1527
  self._ctx.submit_write_batch(WriteBatch([FullRewrite(self._frame, take_ownership=True)]))
1520
1528
  return False
1521
1529
 
@@ -1785,6 +1793,11 @@ def validate_app_install(
1785
1793
 
1786
1794
 
1787
1795
  def _manifest_runtime(*, host_os: str | None = None, host_arch: str | None = None) -> AppRuntime:
1796
+ from luvatrix_core.core.app_runtime import AppRuntime
1797
+ from luvatrix_core.core.hdi_thread import HDIThread
1798
+ from luvatrix_core.core.sensor_manager import SensorManagerThread
1799
+ from luvatrix_core.core.window_matrix import WindowMatrix
1800
+
1788
1801
  return AppRuntime(
1789
1802
  matrix=WindowMatrix(1, 1),
1790
1803
  hdi=HDIThread(source=_NoopHDISource()),
@@ -1835,6 +1848,36 @@ def _normalize_host_os(value: str) -> str:
1835
1848
  return aliases[normalized]
1836
1849
 
1837
1850
 
1851
+ _LAZY_EXPORTS = {
1852
+ "AppContext": ("luvatrix_core.core.app_runtime", "AppContext"),
1853
+ "AppLifecycle": ("luvatrix_core.core.app_runtime", "AppLifecycle"),
1854
+ "AppManifest": ("luvatrix_core.core.app_runtime", "AppManifest"),
1855
+ "AppRuntime": ("luvatrix_core.core.app_runtime", "AppRuntime"),
1856
+ "AppUIRenderer": ("luvatrix_core.core.app_runtime", "AppUIRenderer"),
1857
+ "AppVariant": ("luvatrix_core.core.app_runtime", "AppVariant"),
1858
+ "AppWebConfig": ("luvatrix_core.core.app_runtime", "AppWebConfig"),
1859
+ "ResolvedAppVariant": ("luvatrix_core.core.app_runtime", "ResolvedAppVariant"),
1860
+ "InteractionAwareWorkScheduler": (
1861
+ "luvatrix_core.core.interaction_work",
1862
+ "InteractionAwareWorkScheduler",
1863
+ ),
1864
+ }
1865
+
1866
+
1867
+ def __getattr__(name: str) -> object:
1868
+ target = _LAZY_EXPORTS.get(name)
1869
+ if target is None:
1870
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
1871
+ module_name, attribute_name = target
1872
+ value = getattr(import_module(module_name), attribute_name)
1873
+ globals()[name] = value
1874
+ return value
1875
+
1876
+
1877
+ def __dir__() -> list[str]:
1878
+ return sorted(set(globals()) | _LAZY_EXPORTS.keys())
1879
+
1880
+
1838
1881
  __all__ = [
1839
1882
  "APP_PROTOCOL_VERSION",
1840
1883
  "App",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: luvatrix
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: Luvatrix runtime and plotting toolkit
5
5
  Author: Luvatrix contributors
6
6
  License-Expression: LicenseRef-Proprietary
@@ -107,6 +107,7 @@ luvatrix_core/templates/native/android/gradle.properties
107
107
  luvatrix_core/templates/native/android/gradlew
108
108
  luvatrix_core/templates/native/android/settings.gradle.kts
109
109
  luvatrix_core/templates/native/android/app/build.gradle.kts
110
+ luvatrix_core/templates/native/android/app/proguard-rules.pro
110
111
  luvatrix_core/templates/native/android/app/src/main/AndroidManifest.xml
111
112
  luvatrix_core/templates/native/android/app/src/main/assets/luvatrix_bitmap_font.txt
112
113
  luvatrix_core/templates/native/android/app/src/main/assets/luvatrix_launch_config.json
@@ -114,12 +115,18 @@ luvatrix_core/templates/native/android/app/src/main/assets/luvatrix_matrix_font_
114
115
  luvatrix_core/templates/native/android/app/src/main/cpp/CMakeLists.txt
115
116
  luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_camera_preview.frag
116
117
  luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_camera_preview_shaders.h
118
+ luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_primitive_batch.frag
119
+ luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_primitive_batch.vert
120
+ luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_primitive_batch_shaders.h
117
121
  luvatrix_core/templates/native/android/app/src/main/cpp/luvatrix_vulkan_renderer.cpp
118
122
  luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/CameraBridge.kt
123
+ luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LatestFrameMailbox.kt
119
124
  luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LaunchPerformance.kt
125
+ luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LuvatrixApplication.kt
120
126
  luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/LuvatrixVulkanView.kt
121
127
  luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/MainActivity.kt
122
128
  luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/NativeVulkan.kt
129
+ luvatrix_core/templates/native/android/app/src/main/java/com/luvatrix/app/StartupResources.kt
123
130
  luvatrix_core/templates/native/android/app/src/main/python/.gitignore
124
131
  luvatrix_core/templates/native/android/app/src/main/python/luvatrix_android_boot.py
125
132
  luvatrix_core/templates/native/android/app/src/main/python/luvatrix_launch_config.json
@@ -129,7 +136,14 @@ luvatrix_core/templates/native/android/app/src/main/res/mipmap-xhdpi/ic_launcher
129
136
  luvatrix_core/templates/native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
130
137
  luvatrix_core/templates/native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
131
138
  luvatrix_core/templates/native/android/app/src/main/res/values/styles.xml
139
+ luvatrix_core/templates/native/android/app/src/release/generated/baselineProfiles/baseline-prof.txt
140
+ luvatrix_core/templates/native/android/app/src/release/generated/baselineProfiles/startup-prof.txt
141
+ luvatrix_core/templates/native/android/app/src/test/java/com/luvatrix/app/BootstrapStateTest.kt
132
142
  luvatrix_core/templates/native/android/app/src/test/java/com/luvatrix/app/LaunchTimelineTest.kt
143
+ luvatrix_core/templates/native/android/benchmark/build.gradle.kts
144
+ luvatrix_core/templates/native/android/benchmark/src/main/java/com/luvatrix/benchmark/BaselineProfileGenerator.kt
145
+ luvatrix_core/templates/native/android/benchmark/src/main/java/com/luvatrix/benchmark/StartupBenchmark.kt
146
+ luvatrix_core/templates/native/android/benchmark/src/main/java/com/luvatrix/benchmark/StartupJourney.kt
133
147
  luvatrix_core/templates/native/android/scripts/emulator_acceptance.sh
134
148
  luvatrix_core/templates/native/android/scripts/sync_python_assets.sh
135
149
  luvatrix_core/templates/native/ios/project.yml
@@ -1,4 +1,4 @@
1
1
  """Luvatrix runtime."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.2.4"
4
+ __version__ = "0.2.5"
@@ -266,6 +266,12 @@ def _single_roll_shift(shifts) -> int:
266
266
  return int(shifts)
267
267
 
268
268
 
269
+ def filled_rgba(height: int, width: int, rgba: tuple[int, int, int, int]):
270
+ """Allocate isolated uint8 RGBA storage filled with one color."""
271
+ pixel = from_sequence(list(rgba), (1, 1, 4))
272
+ return broadcast_to_clone(pixel, (int(height), int(width), 4))
273
+
274
+
269
275
  def _normalize_roll_specs(shifts, dims, ndim: int) -> tuple[tuple[int, int], ...]:
270
276
  if isinstance(dims, int):
271
277
  axes = (dims,)
@@ -921,10 +927,8 @@ else:
921
927
 
922
928
  def broadcast_to_clone(x: _PureArray, shape: tuple[int, ...]) -> _PureArray:
923
929
  H, W, C = shape
924
- src = bytes(x._data[:C])
925
- data = bytearray(H * W * C)
926
- for i in range(H * W):
927
- data[i * C:(i + 1) * C] = src
930
+ pixel_bytes = C * _pure_item_size(x)
931
+ data = x._data[:pixel_bytes] * (H * W)
928
932
  return _PureArray(data, shape, x.dtype)
929
933
 
930
934
  def roll(x: _PureArray, shifts, dims=None) -> _PureArray:
@@ -31,6 +31,7 @@ class DisplayRuntime:
31
31
  self._target_started = False
32
32
  self._last_error: Exception | None = None
33
33
  self._last_copy_telemetry: dict[str, int] = {}
34
+ self._last_presented_revision: int | None = None
34
35
 
35
36
  def start(self) -> None:
36
37
  if self._thread and self._thread.is_alive():
@@ -79,17 +80,22 @@ class DisplayRuntime:
79
80
  return None
80
81
 
81
82
  # Coalesce queued blits to newest revision so frame data and revision stay aligned.
83
+ dirty_rect = event.dirty_rect
82
84
  while True:
83
85
  newer = self._matrix.pop_call_blit(timeout=None)
84
86
  if newer is None:
85
87
  break
88
+ dirty_rect = _union_dirty_rects(dirty_rect, newer.dirty_rect)
86
89
  event = newer
87
90
 
88
91
  snapshot = self._matrix.read_revision_snapshot(event.revision)
89
92
  if snapshot is None:
90
93
  snapshot = self._matrix.read_snapshot()
91
- frame = _build_frame(snapshot=snapshot, revision=event.revision)
94
+ if self._last_presented_revision is None:
95
+ dirty_rect = None
96
+ frame = _build_frame(snapshot=snapshot, revision=event.revision, dirty_rect=dirty_rect)
92
97
  self._target.present_frame(frame)
98
+ self._last_presented_revision = event.revision
93
99
  self._last_copy_telemetry = snapshot_copy_telemetry()
94
100
  return RenderTick(event=event, frame=frame)
95
101
 
@@ -122,7 +128,11 @@ class DisplayRuntime:
122
128
  return dict(self._last_copy_telemetry)
123
129
 
124
130
 
125
- def _build_frame(snapshot: object, revision: int) -> DisplayFrame:
131
+ def _build_frame(
132
+ snapshot: object,
133
+ revision: int,
134
+ dirty_rect: tuple[int, int, int, int] | None = None,
135
+ ) -> DisplayFrame:
126
136
  if snapshot.ndim != 3 or snapshot.shape[2] != 4:
127
137
  raise ValueError(f"invalid snapshot shape: {tuple(snapshot.shape)}")
128
138
  if not accel.is_uint8(snapshot):
@@ -133,4 +143,18 @@ def _build_frame(snapshot: object, revision: int) -> DisplayFrame:
133
143
  width=int(width),
134
144
  height=int(height),
135
145
  rgba=snapshot,
146
+ dirty_rect=dirty_rect,
136
147
  )
148
+
149
+
150
+ def _union_dirty_rects(
151
+ first: tuple[int, int, int, int] | None,
152
+ second: tuple[int, int, int, int] | None,
153
+ ) -> tuple[int, int, int, int] | None:
154
+ if first is None or second is None:
155
+ return None
156
+ left = min(first[0], second[0])
157
+ top = min(first[1], second[1])
158
+ right = max(first[0] + first[2], second[0] + second[2])
159
+ bottom = max(first[1] + first[3], second[1] + second[3])
160
+ return (left, top, right - left, bottom - top)
@@ -166,6 +166,8 @@ class UnifiedRuntime:
166
166
  present_fps: int | None = None,
167
167
  display_timeout: float = 0.0,
168
168
  on_targets_started: "Callable[[], None] | None" = None,
169
+ before_lifecycle_init: "Callable[[], None] | None" = None,
170
+ defer_hdi_polling_until_first_frame: bool = False,
169
171
  defer_optional_sensor_polling_until_first_frame: bool = False,
170
172
  ) -> UnifiedRunResult:
171
173
  if max_ticks is not None and max_ticks <= 0:
@@ -173,6 +175,12 @@ class UnifiedRuntime:
173
175
  rate = FrameRateController(target_fps=target_fps, present_fps=present_fps)
174
176
  app_path = Path(app_dir).resolve()
175
177
  manifest = self._app_runtime.load_manifest(app_path)
178
+ effective_render_mode = self._render_mode
179
+ if effective_render_mode == "auto":
180
+ if manifest.render_preferred == "matrix":
181
+ effective_render_mode = "matrix"
182
+ elif manifest.render_preferred == "scene" and self._scene_target is not None:
183
+ effective_render_mode = "scene"
176
184
  debug_policy_profile = self._app_runtime.resolve_debug_policy_profile(manifest)
177
185
  granted = self._app_runtime.resolve_capabilities(manifest)
178
186
  ctx = self._app_runtime.build_context(granted_capabilities=granted, manifest=manifest)
@@ -196,9 +204,9 @@ class UnifiedRuntime:
196
204
  stopped_by_energy_safety = False
197
205
  started = False
198
206
  active_targets: list[object] = []
199
- if self._scene_target is not None and self._render_mode in ("auto", "scene"):
207
+ if self._scene_target is not None and effective_render_mode in ("auto", "scene"):
200
208
  active_targets.append(self._scene_target)
201
- if self._render_mode == "matrix" or (self._render_mode == "auto" and self._scene_target is None):
209
+ if effective_render_mode == "matrix" or (effective_render_mode == "auto" and self._scene_target is None):
202
210
  active_targets.append(self._target)
203
211
  self._configure_target_debug_menu(
204
212
  manifest.app_id,
@@ -218,7 +226,12 @@ class UnifiedRuntime:
218
226
  repeat_latest=False,
219
227
  )
220
228
  scene_present_loop_started = True
221
- ctx.hdi.start()
229
+ required_hdi_capability = any(
230
+ capability.startswith("hdi.") for capability in manifest.required_capabilities
231
+ )
232
+ hdi_started = not (defer_hdi_polling_until_first_frame and not required_hdi_capability)
233
+ if hdi_started:
234
+ ctx.hdi.start()
222
235
  required_sensor_capability = any(
223
236
  capability.startswith("sensor.") for capability in manifest.required_capabilities
224
237
  )
@@ -228,15 +241,22 @@ class UnifiedRuntime:
228
241
  if sensor_manager_started:
229
242
  ctx.sensor_manager.start()
230
243
  deferred_sensor_deadline = time.perf_counter() + 0.1
244
+ deferred_hdi_deadline = time.perf_counter() + 0.1
231
245
  last = time.perf_counter()
232
246
  was_active = self._is_active()
233
247
  try:
248
+ if before_lifecycle_init is not None:
249
+ before_lifecycle_init()
234
250
  lifecycle.init(ctx)
235
251
  tick_idx = 0
236
252
  while max_ticks is None or tick_idx < max_ticks:
237
253
  for active_target in active_targets:
238
254
  active_target.pump_events()
239
- if self._scene_display_runtime is not None and self._scene_display_runtime.last_error is not None:
255
+ if (
256
+ scene_present_loop_started
257
+ and self._scene_display_runtime is not None
258
+ and self._scene_display_runtime.last_error is not None
259
+ ):
240
260
  raise self._scene_display_runtime.last_error
241
261
  if any(bool(active_target.should_close()) for active_target in active_targets):
242
262
  stopped_by_target_close = True
@@ -265,8 +285,7 @@ class UnifiedRuntime:
265
285
  self._app_loop_rate.mark()
266
286
  ticks_run += 1
267
287
  tick_idx += 1
268
- if self._scene_display_runtime is None and rate.should_present(now):
269
- tick = None
288
+ if not scene_present_loop_started and rate.should_present(now):
270
289
  matrix_tick = self._display_runtime.run_once(timeout=display_timeout)
271
290
  if matrix_tick is not None:
272
291
  frames_presented += 1
@@ -274,12 +293,21 @@ class UnifiedRuntime:
274
293
  if not sensor_manager_started:
275
294
  scene_frames = (
276
295
  self._scene_display_runtime.frames_presented
277
- if self._scene_display_runtime is not None
296
+ if scene_present_loop_started and self._scene_display_runtime is not None
278
297
  else 0
279
298
  )
280
299
  if frames_presented > 0 or scene_frames > 0 or time.perf_counter() >= deferred_sensor_deadline:
281
300
  ctx.sensor_manager.start()
282
301
  sensor_manager_started = True
302
+ if not hdi_started:
303
+ scene_frames = (
304
+ self._scene_display_runtime.frames_presented
305
+ if scene_present_loop_started and self._scene_display_runtime is not None
306
+ else 0
307
+ )
308
+ if frames_presented > 0 or scene_frames > 0 or time.perf_counter() >= deferred_hdi_deadline:
309
+ ctx.hdi.start()
310
+ hdi_started = True
283
311
  sleep_for = rate.compute_sleep(
284
312
  loop_started_at=now,
285
313
  loop_finished_at=time.perf_counter(),
@@ -298,7 +326,8 @@ class UnifiedRuntime:
298
326
  try:
299
327
  lifecycle.stop(ctx)
300
328
  finally:
301
- ctx.hdi.stop()
329
+ if hdi_started:
330
+ ctx.hdi.stop()
302
331
  ctx.sensor_manager.stop()
303
332
  if started:
304
333
  if scene_present_loop_started and self._scene_display_runtime is not None:
@@ -307,7 +336,7 @@ class UnifiedRuntime:
307
336
  self._scene_display_runtime.run_once(timeout=0.0, repeat_latest=True)
308
337
  for active_target in reversed(active_targets):
309
338
  active_target.stop()
310
- if self._scene_display_runtime is not None:
339
+ if scene_present_loop_started and self._scene_display_runtime is not None:
311
340
  frames_presented = self._scene_display_runtime.frames_presented
312
341
  self._frames_presented = frames_presented
313
342
  return UnifiedRunResult(
@@ -84,16 +84,31 @@ class CallBlitEvent:
84
84
  event_id: int
85
85
  revision: int
86
86
  ts_ns: int
87
+ # ``None`` denotes a global rewrite or transform that invalidates all pixels.
88
+ dirty_rect: tuple[int, int, int, int] | None = None
87
89
 
88
90
 
89
91
  class WindowMatrix:
90
- """Canonical RGBA255 matrix with atomic write-batch commits."""
92
+ """Canonical RGBA255 matrix with atomic write-batch commits.
91
93
 
92
- def __init__(self, height: int, width: int, background: tuple[int, int, int, int] = (0, 0, 0, 255)) -> None:
94
+ ``lazy=True`` keeps dimensions and event state available for scene-only
95
+ runtimes without allocating RGBA storage. The first matrix read or write
96
+ materializes storage; a first full rewrite directly becomes that storage.
97
+ """
98
+
99
+ def __init__(
100
+ self,
101
+ height: int,
102
+ width: int,
103
+ background: tuple[int, int, int, int] = (0, 0, 0, 255),
104
+ *,
105
+ lazy: bool = False,
106
+ ) -> None:
93
107
  if height <= 0 or width <= 0:
94
108
  raise ValueError("height and width must be > 0")
95
109
  self.height = height
96
110
  self.width = width
111
+ self._background = tuple(background)
97
112
  self._write_lock = threading.Lock()
98
113
  self._event_lock = threading.Lock()
99
114
  self._event_cv = threading.Condition(self._event_lock)
@@ -103,20 +118,26 @@ class WindowMatrix:
103
118
  self._revision_snapshot_enabled = os.getenv("LUVATRIX_ENABLE_REVISIONED_SNAPSHOT", "0").strip() == "1"
104
119
  self._revision_snapshot: object | None = None
105
120
  self._revision_snapshot_revision = 0
106
- bg = accel.from_sequence(list(background), (1, 1, 4))
107
- self._matrix = accel.broadcast_to_clone(bg, (height, width, 4))
108
- if self._revision_snapshot_enabled:
109
- self._revision_snapshot = accel.clone(self._matrix)
121
+ self._matrix: object | None = None
122
+ if not lazy:
123
+ self._materialize_locked()
110
124
 
111
125
  @property
112
126
  def revision(self) -> int:
113
127
  return self._revision
114
128
 
129
+ @property
130
+ def is_materialized(self) -> bool:
131
+ """Whether the RGBA backing store has been allocated."""
132
+ with self._write_lock:
133
+ return self._matrix is not None
134
+
115
135
  def read_snapshot(self) -> object:
116
136
  """Safe read view for external consumers."""
117
137
  with self._write_lock:
138
+ matrix = self._materialize_locked()
118
139
  started = time.perf_counter_ns()
119
- out = accel.clone(self._matrix)
140
+ out = accel.clone(matrix)
120
141
  add_copy_telemetry(
121
142
  copy_count=1,
122
143
  copy_bytes=accel.numel(out),
@@ -129,6 +150,7 @@ class WindowMatrix:
129
150
  with self._write_lock:
130
151
  if not self._revision_snapshot_enabled:
131
152
  return None
153
+ self._materialize_locked()
132
154
  if self._revision_snapshot is None:
133
155
  return None
134
156
  if int(revision) != int(self._revision_snapshot_revision):
@@ -137,13 +159,26 @@ class WindowMatrix:
137
159
 
138
160
  def _unsafe_matrix_view(self) -> object:
139
161
  """Internal-only no-copy handle."""
140
- return self._matrix
162
+ with self._write_lock:
163
+ return self._materialize_locked()
164
+
165
+ def _materialize_locked(self) -> object:
166
+ matrix = self._matrix
167
+ if matrix is not None:
168
+ return matrix
169
+ matrix = accel.filled_rgba(self.height, self.width, self._background)
170
+ self._matrix = matrix
171
+ if self._revision_snapshot_enabled:
172
+ self._revision_snapshot = accel.clone(matrix)
173
+ self._revision_snapshot_revision = int(self._revision)
174
+ return matrix
141
175
 
142
176
  def submit_write_batch(self, batch: WriteBatch) -> CallBlitEvent:
143
177
  if not batch.operations:
144
178
  raise ValueError("write batch must include at least one operation")
145
179
 
146
180
  with self._write_lock:
181
+ dirty_rect = _dirty_rect_for_operations(batch.operations, self.width, self.height)
147
182
  offending_pixels = 0
148
183
  if len(batch.operations) == 1 and isinstance(batch.operations[0], FullRewrite):
149
184
  op = batch.operations[0]
@@ -154,10 +189,10 @@ class WindowMatrix:
154
189
  )
155
190
  elif self._is_localized_commit_batch(batch.operations):
156
191
  prepared, offending_pixels = self._prepare_localized_ops(batch.operations)
157
- self._apply_prepared_localized_ops(prepared)
192
+ self._apply_prepared_localized_ops(self._materialize_locked(), prepared)
158
193
  else:
159
194
  stage_started = time.perf_counter_ns()
160
- staged = accel.clone(self._matrix)
195
+ staged = accel.clone(self._materialize_locked())
161
196
  add_copy_telemetry(
162
197
  copy_count=1,
163
198
  copy_bytes=accel.numel(staged),
@@ -180,6 +215,7 @@ class WindowMatrix:
180
215
  event_id=self._next_event_id,
181
216
  revision=self._revision,
182
217
  ts_ns=time.time_ns(),
218
+ dirty_rect=dirty_rect,
183
219
  )
184
220
  self._next_event_id += 1
185
221
 
@@ -299,22 +335,28 @@ class WindowMatrix:
299
335
  raise TypeError(f"Unsupported localized write op: {type(op)!r}")
300
336
  return prepared, offending_pixels
301
337
 
302
- def _apply_prepared_localized_ops(self, prepared: list[tuple[str, int, int, int, int, object]]) -> None:
338
+ def _apply_prepared_localized_ops(
339
+ self,
340
+ matrix: object,
341
+ prepared: list[tuple[str, int, int, int, int, object]],
342
+ ) -> None:
303
343
  for kind, x, y, width, height, payload in prepared:
304
344
  if kind == "column":
305
- self._matrix[:, x, :] = payload
345
+ matrix[:, x, :] = payload
306
346
  continue
307
347
  if kind == "row":
308
- self._matrix[y, :, :] = payload
348
+ matrix[y, :, :] = payload
309
349
  continue
310
350
  if kind == "rect":
311
- self._matrix[y : y + height, x : x + width, :] = payload
351
+ matrix[y : y + height, x : x + width, :] = payload
312
352
  continue
313
353
  raise RuntimeError(f"Unsupported prepared localized write kind: {kind}")
314
354
 
315
355
  def _refresh_revision_snapshot(self) -> None:
316
356
  if not self._revision_snapshot_enabled:
317
357
  return
358
+ if self._matrix is None:
359
+ return
318
360
  started = time.perf_counter_ns()
319
361
  self._revision_snapshot = accel.clone(self._matrix)
320
362
  self._revision_snapshot_revision = int(self._revision)
@@ -339,6 +381,49 @@ def _validate_rect(x: int, y: int, width: int, height: int, matrix_width: int, m
339
381
  raise ValueError("rect exceeds matrix bounds")
340
382
 
341
383
 
384
+ def _dirty_rect_for_operations(
385
+ operations: list[WriteOp],
386
+ matrix_width: int,
387
+ matrix_height: int,
388
+ ) -> tuple[int, int, int, int] | None:
389
+ """Return the affected pixel bounds, or ``None`` when every pixel changed."""
390
+ rect: tuple[int, int, int, int] | None = (0, 0, 0, 0)
391
+ for operation in operations:
392
+ if isinstance(operation, (FullRewrite, ShiftFrame, Multiply)):
393
+ return None
394
+ if isinstance(operation, ReplaceRect):
395
+ candidate = (operation.x, operation.y, operation.width, operation.height)
396
+ elif isinstance(operation, ReplaceColumn):
397
+ candidate = (operation.index, 0, 1, matrix_height)
398
+ elif isinstance(operation, PushColumn):
399
+ candidate = (operation.index, 0, matrix_width - operation.index, matrix_height)
400
+ elif isinstance(operation, ReplaceRow):
401
+ candidate = (0, operation.index, matrix_width, 1)
402
+ elif isinstance(operation, PushRow):
403
+ candidate = (0, operation.index, matrix_width, matrix_height - operation.index)
404
+ else:
405
+ return None
406
+ rect = _union_dirty_rect(rect, candidate)
407
+ return rect
408
+
409
+
410
+ def _union_dirty_rect(
411
+ first: tuple[int, int, int, int] | None,
412
+ second: tuple[int, int, int, int] | None,
413
+ ) -> tuple[int, int, int, int] | None:
414
+ if first is None or second is None:
415
+ return None
416
+ if first[2] == 0 or first[3] == 0:
417
+ return second
418
+ if second[2] == 0 or second[3] == 0:
419
+ return first
420
+ left = min(first[0], second[0])
421
+ top = min(first[1], second[1])
422
+ right = max(first[0] + first[2], second[0] + second[2])
423
+ bottom = max(first[1] + first[3], second[1] + second[3])
424
+ return (left, top, right - left, bottom - top)
425
+
426
+
342
427
  def _coerce_float32(value: object, expected_shape: tuple[int, ...], label: str) -> object:
343
428
  return accel.coerce_float32(value, expected_shape, label)
344
429
 
@@ -86,12 +86,14 @@ def write_android_launch_config(
86
86
  _ensure_android_generated_gitignore(project)
87
87
  dest = project / "app" / "src" / "main" / "assets" / "luvatrix_launch_config.json"
88
88
  dest.parent.mkdir(parents=True, exist_ok=True)
89
- display = _read_app_display_config(app_dir)
89
+ manifest = _read_app_manifest(app_dir)
90
+ display = _read_app_display_config(manifest)
91
+ resolved_render_mode = _resolve_manifest_render_mode(render_mode, manifest)
90
92
  data = {
91
93
  "app_dir": "luvatrix_app",
92
94
  "source_app_dir": str(app_dir),
93
95
  "render_scale": float(render_scale),
94
- "render_mode": render_mode,
96
+ "render_mode": resolved_render_mode,
95
97
  "target_fps": target_fps,
96
98
  "present_fps": present_fps,
97
99
  "low_latency_mode": bool(low_latency_mode),
@@ -126,12 +128,15 @@ def _ensure_android_generated_gitignore(project_dir: Path) -> Path:
126
128
  return gitignore
127
129
 
128
130
 
129
- def _read_app_display_config(app_dir: Path) -> dict[str, int]:
130
- manifest = app_dir / "app.toml"
131
+ def _read_app_manifest(app_dir: Path) -> dict[str, object]:
131
132
  try:
132
- raw = tomllib.loads(manifest.read_text(encoding="utf-8"))
133
+ raw = tomllib.loads((app_dir / "app.toml").read_text(encoding="utf-8"))
133
134
  except Exception:
134
135
  return {}
136
+ return raw if isinstance(raw, dict) else {}
137
+
138
+
139
+ def _read_app_display_config(raw: dict[str, object]) -> dict[str, int]:
135
140
  display = raw.get("display")
136
141
  if not isinstance(display, dict):
137
142
  return {}
@@ -146,6 +151,16 @@ def _read_app_display_config(app_dir: Path) -> dict[str, int]:
146
151
  return out
147
152
 
148
153
 
154
+ def _resolve_manifest_render_mode(requested: str, raw: dict[str, object]) -> str:
155
+ if requested != "auto":
156
+ return requested
157
+ render = raw.get("render")
158
+ if not isinstance(render, dict):
159
+ return requested
160
+ preferred = str(render.get("preferred") or "").strip()
161
+ return preferred if preferred in ("matrix", "scene") else requested
162
+
163
+
149
164
  def _android_device_logical_display_config(*, device_id: str | None = None) -> dict[str, int]:
150
165
  adb = shutil.which("adb")
151
166
  if adb is None: