@glassly/bluetooth-sdk 0.1.0-dev.98 → 0.2.8-dev.444
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.
- package/README.md +47 -6
- package/android/build.gradle +7 -7
- package/android/lc3Lib/build.gradle +2 -2
- package/android/src/main/assets/G2Firmware/README.md +27 -2
- package/android/src/main/assets/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
- package/android/src/main/assets/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +50 -14
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +139 -10
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +28 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/DisplayPacer.kt +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +10 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +4 -4
- package/android/src/main/java/com/glassly/bluetoothsdk/GlassesNotifications.kt +155 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +1075 -257
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2BleLink.kt +119 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +29 -28
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +138 -33
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwScene.kt +673 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +235 -162
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransport.kt +402 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnostics.kt +84 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +3 -3
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +9 -9
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Keyboard.kt +689 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardBridge.kt +452 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardHid.kt +208 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFile.kt +181 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2RingBattery.kt +20 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Sensors.kt +67 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2TouchInput.kt +39 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +5 -5
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +14 -14
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/{GlasslyBle.java → NexProto.java} +6462 -7618
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIds.kt +58 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +77 -2
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +55 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +24 -149
- package/android/src/test/java/com/glassly/bluetoothsdk/DisplayPacerTest.kt +277 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/G2BleLinkTest.kt +70 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +45 -56
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +78 -5
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwSceneTest.kt +489 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +100 -43
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransportTest.kt +252 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnosticsTest.kt +64 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardTest.kt +271 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFileTest.kt +204 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2RingBatteryTest.kt +56 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2SensorsTest.kt +56 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2TouchInputTest.kt +60 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NexProtoDescriptorTest.kt +32 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIdsTest.kt +58 -0
- package/build/BluetoothSdk.types.d.ts +209 -20
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +1 -1
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +30 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/generated/changelogCatalog.d.ts +28 -1
- package/build/generated/changelogCatalog.d.ts.map +1 -1
- package/build/generated/changelogCatalog.js +37 -1
- package/build/generated/changelogCatalog.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +14 -0
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +102 -13
- package/ios/GlasslyBluetoothSDK.podspec +1 -1
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +8 -3
- package/ios/Source/BluetoothSDK.swift +53 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +13 -0
- package/ios/Source/DeviceManager.swift +151 -33
- package/ios/Source/DeviceStore.swift +28 -14
- package/ios/Source/DisplayPacer.swift +180 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +10 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +4 -4
- package/ios/Source/GlassesNotifications.swift +181 -0
- package/ios/Source/ObservableStore.swift +14 -2
- package/ios/Source/Resources/G2Firmware/README.md +27 -2
- package/ios/Source/Resources/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
- package/ios/Source/Resources/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
- package/ios/Source/controllers/R1.swift +0 -1
- package/ios/Source/internal/BluetoothAvailability.swift +31 -0
- package/ios/Source/sgcs/G2.swift +1434 -504
- package/ios/Source/sgcs/G2AncsRelay.swift +268 -0
- package/ios/Source/sgcs/G2BleLink.swift +102 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +45 -27
- package/ios/Source/sgcs/G2CfwRenderer.swift +166 -33
- package/ios/Source/sgcs/G2CfwScene.swift +779 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +244 -117
- package/ios/Source/sgcs/G2CfwTransport.swift +485 -0
- package/ios/Source/sgcs/G2DisplayDiagnostics.swift +154 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +2 -2
- package/ios/Source/sgcs/G2Flasher.swift +9 -9
- package/ios/Source/sgcs/G2Keyboard.swift +780 -0
- package/ios/Source/sgcs/G2KeyboardBridge.swift +438 -0
- package/ios/Source/sgcs/G2KeyboardHid.swift +290 -0
- package/ios/Source/sgcs/G2NotificationTuning.swift +24 -0
- package/ios/Source/sgcs/G2RingBattery.swift +22 -0
- package/ios/Source/sgcs/G2Sensors.swift +75 -0
- package/ios/Source/sgcs/G2TouchInput.swift +51 -0
- package/ios/Source/sgcs/G2WritePacing.swift +18 -0
- package/ios/Source/sgcs/MentraNex.swift +44 -44
- package/ios/Source/sgcs/SGCManager.swift +120 -13
- package/ios/Source/sgcs/{glassly_ble.pb.swift → nex_ble.pb.swift} +405 -465
- package/ios/Source/speakerid/SpeakerIdEngine.swift +2 -2
- package/ios/Source/status/DeviceStatus.swift +27 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +15 -14
- package/ios/Source/utils/Enums.swift +0 -1
- package/ios/Source/utils/G1Text.swift +0 -1
- package/ios/Source/utils/G2NotifyFile.swift +20 -10
- package/ios/Source/utils/JSCExperiment.swift +6 -6
- package/ios/Source/utils/Models.swift +0 -1
- package/ios/Tests/BluetoothSDKTests/BluetoothAvailabilityTests.swift +29 -0
- package/ios/Tests/BluetoothSDKTests/DisplayPacerTests.swift +255 -0
- package/ios/Tests/BluetoothSDKTests/G2AncsRelayTests.swift +186 -0
- package/ios/Tests/BluetoothSDKTests/G2BleLinkTests.swift +52 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +46 -51
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +164 -5
- package/ios/Tests/BluetoothSDKTests/G2CfwSceneTests.swift +497 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +108 -44
- package/ios/Tests/BluetoothSDKTests/G2CfwTransportTests.swift +259 -0
- package/ios/Tests/BluetoothSDKTests/G2DisplayDiagnosticsTests.swift +89 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +3 -2
- package/ios/Tests/BluetoothSDKTests/G2ImageAckTests.swift +104 -0
- package/ios/Tests/BluetoothSDKTests/G2KeyboardTests.swift +274 -0
- package/ios/Tests/BluetoothSDKTests/G2NotifyFileTests.swift +21 -5
- package/ios/Tests/BluetoothSDKTests/G2RingBatteryTests.swift +56 -0
- package/ios/Tests/BluetoothSDKTests/G2SensorsTests.swift +56 -0
- package/ios/Tests/BluetoothSDKTests/G2TouchInputTests.swift +71 -0
- package/ios/Tests/BluetoothSDKTests/G2WritePacingTests.swift +35 -0
- package/ios/Tests/BluetoothSDKTests/GlassesNotificationTests.swift +95 -0
- package/package.json +1 -1
- package/plugin/build/withAndroid.js +2 -2
- package/plugin/build/withIos.js +1 -1
- package/scripts/gen-nex-proto.sh +12 -0
- package/scripts/inject-ios-sdk-version.mjs +1 -1
- package/src/BluetoothSdk.types.ts +214 -19
- package/src/_internal.ts +1 -1
- package/src/_private/BluetoothSdkModule.ts +40 -0
- package/src/generated/changelogCatalog.ts +37 -1
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +24 -0
|
@@ -27,6 +27,11 @@ actor G2CfwRenderer {
|
|
|
27
27
|
let height: Int32
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/// The global setting is 1...3; the firmware placement tables are zero-based.
|
|
31
|
+
static func depthLevel(forSetting depth: Int) -> Int {
|
|
32
|
+
min(max(depth, 1), 3) - 1
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
static func lensBoxes(
|
|
31
36
|
canvasWidth: Int32, canvasHeight: Int32, heightLevel: Int, depthLevel: Int
|
|
32
37
|
) -> LensBoxes {
|
|
@@ -54,13 +59,138 @@ actor G2CfwRenderer {
|
|
|
54
59
|
|
|
55
60
|
// MARK: - Grid-exact mono glyphs (texture-cache path)
|
|
56
61
|
|
|
57
|
-
///
|
|
62
|
+
/// Asset-store mirror. Modes 19/20 draw at absolute panel coordinates
|
|
58
63
|
/// (no per-lens variant), so the caller only enables this path on frames
|
|
59
|
-
/// whose window is placed lens-equal. The
|
|
60
|
-
/// caller's generation changes (lease cycled /
|
|
61
|
-
///
|
|
64
|
+
/// whose window is placed lens-equal. The mirror resets whenever the
|
|
65
|
+
/// caller's generation changes (lease cycled / cache reset); the glyph
|
|
66
|
+
/// raster memo survives resets — rasters don't change.
|
|
62
67
|
private let textureCache = G2CfwTextureCache()
|
|
63
68
|
private var glyphRasterMemo: [String: Data] = [:]
|
|
69
|
+
/// Retained object cache (modes 37-41); shares the asset mirror with the
|
|
70
|
+
/// raster path. Two lens mirrors: while every frame is flat only the left
|
|
71
|
+
/// one is encoded and its SHOW goes to both lenses; the first frame with
|
|
72
|
+
/// global or element depth copies it into the right mirror (`stereoSplit`) and from
|
|
73
|
+
/// then on each lens gets its own SHOW whenever the two encodings differ.
|
|
74
|
+
private let scenes = [G2CfwScene(), G2CfwScene()]
|
|
75
|
+
private var stereoSplit = false
|
|
76
|
+
|
|
77
|
+
/// One frame of the object cache: the SHOWs to send, in order, each with
|
|
78
|
+
/// its lens targets, after the asset `uploads`.
|
|
79
|
+
struct StereoEncoded {
|
|
80
|
+
struct Show: Equatable {
|
|
81
|
+
let payload: Data
|
|
82
|
+
let lenses: UInt8
|
|
83
|
+
}
|
|
84
|
+
let shows: [Show]
|
|
85
|
+
let uploads: [Data]
|
|
86
|
+
/// False when the frame is the one already on screen (nothing new to send).
|
|
87
|
+
let changed: Bool
|
|
88
|
+
let puts: Int
|
|
89
|
+
let hits: Int
|
|
90
|
+
let tweens: Int
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// The panel went blank while the cache stayed: hidden objects that were
|
|
94
|
+
/// mid-animation are re-snapped on their next show.
|
|
95
|
+
func sceneHidden() {
|
|
96
|
+
for scene in scenes {
|
|
97
|
+
scene.hidden()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// The RESET reply named the store each lens could allocate; predict against the smaller one.
|
|
102
|
+
func setStoreCapacity(bytes: Int) {
|
|
103
|
+
textureCache.storeCapacity = max(G2CfwTextureCache.granule, min(G2CfwTextureCache.storeSize, bytes))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// A lens answered MISSING: these ids (objects or assets) are redefined on the next frame.
|
|
107
|
+
func forget(ids: [UInt32]) {
|
|
108
|
+
for scene in scenes {
|
|
109
|
+
scene.forget(objectIds: ids)
|
|
110
|
+
}
|
|
111
|
+
textureCache.forget(assetIds: ids)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// Upload payloads that were dropped or failed: their assets may not be resident.
|
|
115
|
+
func forgetUploads(_ payloads: [Data]) {
|
|
116
|
+
textureCache.forget(assetIds: payloads.flatMap(G2CfwTextureCache.assetIds(inUploadPayload:)))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Encode `frame` as SHOWs of the object cache (plus the asset PUT
|
|
120
|
+
/// messages that must precede them): one SHOW for both lenses while the
|
|
121
|
+
/// lens mirrors agree, else one per lens (element `depth` shifts the two
|
|
122
|
+
/// encodings apart). `invalidate` means residency is unknown (a send
|
|
123
|
+
/// failed): every object the frame shows is redefined, and the mirrors
|
|
124
|
+
/// re-converge. Nil when the frame does not fit the object table or the
|
|
125
|
+
/// asset store.
|
|
126
|
+
func encodeVector(
|
|
127
|
+
_ frame: SceneFrame, boxes: LensBoxes, cacheGeneration: Int, invalidate: Bool, inlineText: Bool,
|
|
128
|
+
tag: Int? = nil
|
|
129
|
+
) -> StereoEncoded? {
|
|
130
|
+
if textureCache.generation != cacheGeneration {
|
|
131
|
+
textureCache.reset(generation: cacheGeneration)
|
|
132
|
+
}
|
|
133
|
+
if scenes[0].generation != textureCache.generation {
|
|
134
|
+
for scene in scenes {
|
|
135
|
+
scene.reset(generation: textureCache.generation)
|
|
136
|
+
}
|
|
137
|
+
stereoSplit = false
|
|
138
|
+
} else if invalidate {
|
|
139
|
+
for scene in scenes {
|
|
140
|
+
scene.invalidate()
|
|
141
|
+
}
|
|
142
|
+
stereoSplit = false
|
|
143
|
+
}
|
|
144
|
+
// Both mirrors rasterize the same element pixels: do it once per frame.
|
|
145
|
+
var rasters: [String: Data?] = [:]
|
|
146
|
+
let rasterize: (SceneElement, Int, Int) -> Data? = { element, w, h in
|
|
147
|
+
let key = "\(element.id)|\(element.contentHash)|\(w)x\(h)"
|
|
148
|
+
if let hit = rasters[key] { return hit }
|
|
149
|
+
let pixels = Self.rasterize(
|
|
150
|
+
SceneFrame(
|
|
151
|
+
appId: frame.appId, epoch: frame.epoch, replay: false,
|
|
152
|
+
elements: [element.atOrigin()], removed: [],
|
|
153
|
+
canvasWidth: Int32(w), canvasHeight: Int32(h)
|
|
154
|
+
),
|
|
155
|
+
width: w, height: h
|
|
156
|
+
)
|
|
157
|
+
rasters[key] = pixels
|
|
158
|
+
return pixels
|
|
159
|
+
}
|
|
160
|
+
let mono = G2CfwScene.MonoFace(metrics: Self.monoCellMetrics) {
|
|
161
|
+
Self.monoGlyphRaster($0, fontSize: $1, cellWidth: $2, lineHeight: $3)
|
|
162
|
+
}
|
|
163
|
+
let ox = Int(boxes.left.x)
|
|
164
|
+
let oy = Int(boxes.left.y)
|
|
165
|
+
if !stereoSplit, boxes.left != boxes.right || frame.elements.contains(where: { $0.depth != 0 }) {
|
|
166
|
+
scenes[1].adopt(scenes[0])
|
|
167
|
+
stereoSplit = true
|
|
168
|
+
}
|
|
169
|
+
guard let left = scenes[0].encode(frame, originX: ox, originY: oy, inlineText: inlineText, mono: mono,
|
|
170
|
+
tag: tag, parallax: stereoSplit ? 1 : 0, cache: textureCache, rasterize: rasterize)
|
|
171
|
+
else { return nil }
|
|
172
|
+
guard stereoSplit else {
|
|
173
|
+
return StereoEncoded(shows: [.init(payload: left.payload, lenses: G2CfwTransport.lensBoth)],
|
|
174
|
+
uploads: left.uploads, changed: left.changed,
|
|
175
|
+
puts: left.puts, hits: left.hits, tweens: left.tweens)
|
|
176
|
+
}
|
|
177
|
+
guard let right = scenes[1].encode(frame, originX: Int(boxes.right.x), originY: Int(boxes.right.y), inlineText: inlineText, mono: mono,
|
|
178
|
+
tag: tag, parallax: -1, cache: textureCache, rasterize: rasterize)
|
|
179
|
+
else { return nil }
|
|
180
|
+
var shows: [StereoEncoded.Show] = []
|
|
181
|
+
if left.payload == right.payload {
|
|
182
|
+
shows.append(.init(payload: left.payload, lenses: G2CfwTransport.lensBoth))
|
|
183
|
+
} else {
|
|
184
|
+
// A lens whose frame did not change keeps quiet, unless neither changed
|
|
185
|
+
// (then both SHOWs go, so a caller that always sends stays correct).
|
|
186
|
+
let both = left.changed == right.changed
|
|
187
|
+
if both || left.changed { shows.append(.init(payload: left.payload, lenses: G2CfwTransport.lensLeft)) }
|
|
188
|
+
if both || right.changed { shows.append(.init(payload: right.payload, lenses: G2CfwTransport.lensRight)) }
|
|
189
|
+
}
|
|
190
|
+
return StereoEncoded(shows: shows, uploads: left.uploads + right.uploads, changed: left.changed || right.changed,
|
|
191
|
+
puts: left.puts + right.puts, hits: left.hits + right.hits, tweens: left.tweens + right.tweens)
|
|
192
|
+
}
|
|
193
|
+
|
|
64
194
|
|
|
65
195
|
/// Fixed cell metrics of the phone's monospaced face at `fontSize`.
|
|
66
196
|
static func monoCellMetrics(fontSize: Int32) -> (cellWidth: Int, lineHeight: Int) {
|
|
@@ -259,13 +389,17 @@ actor G2CfwRenderer {
|
|
|
259
389
|
let lensEqual = boxes.left == boxes.right
|
|
260
390
|
let useTexGlyphs = textureGlyphsEnabled && lensEqual
|
|
261
391
|
let useTexImages = textureImagesEnabled && lensEqual
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
if useTexGlyphs || useTexImages, textureCache.generation != cacheGeneration || textureCache.exhausted {
|
|
392
|
+
// The asset mirror follows the caller's generation (a lost or reset
|
|
393
|
+
// on-glass cache); the glasses evict by themselves otherwise.
|
|
394
|
+
if useTexGlyphs || useTexImages, textureCache.generation != cacheGeneration {
|
|
266
395
|
textureCache.reset(generation: cacheGeneration)
|
|
396
|
+
for scene in scenes {
|
|
397
|
+
scene.reset(generation: cacheGeneration)
|
|
398
|
+
}
|
|
399
|
+
stereoSplit = false
|
|
267
400
|
}
|
|
268
401
|
|
|
402
|
+
|
|
269
403
|
var grids: [MonoGrid] = []
|
|
270
404
|
let raster: Data
|
|
271
405
|
if useTexGlyphs {
|
|
@@ -528,7 +662,13 @@ actor G2CfwRenderer {
|
|
|
528
662
|
if let grid = grids.filter({ $0.covers(rect) }).max(by: { $0.z < $1.z }),
|
|
529
663
|
!elementBoxes.contains(where: { $0.z > grid.z && $0.intersects(rect) }),
|
|
530
664
|
let font = textureCache.font(
|
|
531
|
-
fontSize: grid.fontSize, cellWidth: grid.cellWidth, lineHeight: grid.lineHeight
|
|
665
|
+
fontSize: grid.fontSize, cellWidth: grid.cellWidth, lineHeight: grid.lineHeight,
|
|
666
|
+
rasterize: { code in
|
|
667
|
+
self.memoizedGlyphRaster(
|
|
668
|
+
Unicode.Scalar(UInt32(code))!, fontSize: grid.fontSize,
|
|
669
|
+
cellWidth: grid.cellWidth, lineHeight: grid.lineHeight
|
|
670
|
+
)
|
|
671
|
+
}
|
|
532
672
|
)
|
|
533
673
|
{
|
|
534
674
|
let r0 = (rect.y - grid.originY) / grid.lineHeight
|
|
@@ -562,14 +702,7 @@ actor G2CfwRenderer {
|
|
|
562
702
|
}
|
|
563
703
|
for col in c0 ... c1 {
|
|
564
704
|
let scalar = grid.scalar(row: row, col: col)
|
|
565
|
-
if scalar.value >= 32, scalar.value <= 126,
|
|
566
|
-
textureCache.ensureGlyph(font: font, char: UInt8(scalar.value), rasterize: {
|
|
567
|
-
self.memoizedGlyphRaster(
|
|
568
|
-
scalar, fontSize: grid.fontSize,
|
|
569
|
-
cellWidth: grid.cellWidth, lineHeight: grid.lineHeight
|
|
570
|
-
)
|
|
571
|
-
})
|
|
572
|
-
{
|
|
705
|
+
if scalar.value >= 32, scalar.value <= 126, font.glyphs.contains(UInt8(scalar.value)) {
|
|
573
706
|
flushResidual(endCol: col)
|
|
574
707
|
if runChars.isEmpty { runStartCol = col }
|
|
575
708
|
runChars.append(UInt8(scalar.value))
|
|
@@ -591,7 +724,7 @@ actor G2CfwRenderer {
|
|
|
591
724
|
var payloads: [Data] = []
|
|
592
725
|
for run in newRuns {
|
|
593
726
|
guard let payload = G2CfwTextureCache.stringRunPayload(
|
|
594
|
-
|
|
727
|
+
fontId: font.id,
|
|
595
728
|
x: Int(boxes.left.x) + run.x, y: Int(boxes.left.y) + run.y,
|
|
596
729
|
options: G2CfwTextureCache.opaqueDrawOptions, chars: run.chars
|
|
597
730
|
) else { payloads = []; break }
|
|
@@ -627,9 +760,9 @@ actor G2CfwRenderer {
|
|
|
627
760
|
))
|
|
628
761
|
}
|
|
629
762
|
}
|
|
630
|
-
if let
|
|
763
|
+
if let asset = textureCache.ensureImage(hash: candidate.key, gray: gray, width: w, height: h),
|
|
631
764
|
let draw = G2CfwTextureCache.imageDrawPayload(
|
|
632
|
-
|
|
765
|
+
assetId: asset,
|
|
633
766
|
x: Int(boxes.left.x) + candidate.box.x0, y: Int(boxes.left.y) + candidate.box.y0,
|
|
634
767
|
options: G2CfwTextureCache.opaqueDrawOptions
|
|
635
768
|
)
|
|
@@ -1046,12 +1179,15 @@ actor G2CfwRenderer {
|
|
|
1046
1179
|
width: sourceSize.width * imageScale,
|
|
1047
1180
|
height: sourceSize.height * imageScale
|
|
1048
1181
|
)
|
|
1182
|
+
// The ink level scales the pixels, as the texture ramp does on vector firmware.
|
|
1183
|
+
let alpha = element.color < 0 ? 1 : CGFloat(max(0, min(15, element.color))) / 15
|
|
1049
1184
|
source.draw(
|
|
1050
1185
|
in: CGRect(
|
|
1051
1186
|
x: rect.midX - target.width / 2,
|
|
1052
1187
|
y: rect.midY - target.height / 2,
|
|
1053
1188
|
width: target.width, height: target.height
|
|
1054
|
-
)
|
|
1189
|
+
),
|
|
1190
|
+
blendMode: .normal, alpha: alpha
|
|
1055
1191
|
)
|
|
1056
1192
|
}
|
|
1057
1193
|
|
|
@@ -1102,9 +1238,9 @@ actor G2CfwRenderer {
|
|
|
1102
1238
|
return rendered ? grayscale : nil
|
|
1103
1239
|
}
|
|
1104
1240
|
|
|
1105
|
-
/// Mode 3 with the lenses-differ bit: one
|
|
1106
|
-
///
|
|
1107
|
-
/// `[0x83][lx/4][ly/2][w/4][h/2][rx/4][ry/2][w/4][h/2][fid lo][fid hi][
|
|
1241
|
+
/// Mode 3 with the lenses-differ bit: one RLE(4bpp) payload of the
|
|
1242
|
+
/// window drawn at each lens's box (the SID-0xf0 transport compresses).
|
|
1243
|
+
/// `[0x83][lx/4][ly/2][w/4][h/2][rx/4][ry/2][w/4][h/2][fid lo][fid hi][rle]`
|
|
1108
1244
|
static func stereoFrame(pixels: Data, boxes: LensBoxes, fid: UInt16) -> Data? {
|
|
1109
1245
|
guard pixels.count == Int(boxes.width) * Int(boxes.height) else { return nil }
|
|
1110
1246
|
let rle = rleEncode(pack4Bpp(pixels))
|
|
@@ -1129,7 +1265,7 @@ actor G2CfwRenderer {
|
|
|
1129
1265
|
private static func stereoRleFrame(
|
|
1130
1266
|
rle: Data, lx: Int, ly: Int, rx: Int, ry: Int, w: Int32, h: Int32, fid: UInt16
|
|
1131
1267
|
) -> Data? {
|
|
1132
|
-
guard
|
|
1268
|
+
guard !rle.isEmpty else { return nil }
|
|
1133
1269
|
var payload = Data([0x83])
|
|
1134
1270
|
for (x, y) in [(lx, ly), (rx, ry)] {
|
|
1135
1271
|
payload.append(UInt8(x / 4))
|
|
@@ -1139,7 +1275,8 @@ actor G2CfwRenderer {
|
|
|
1139
1275
|
}
|
|
1140
1276
|
payload.append(UInt8(fid & 0xFF))
|
|
1141
1277
|
payload.append(UInt8(fid >> 8))
|
|
1142
|
-
|
|
1278
|
+
// Plain RLE: the SID-0xf0 transport deflates the whole message.
|
|
1279
|
+
payload.append(rle)
|
|
1143
1280
|
return payload
|
|
1144
1281
|
}
|
|
1145
1282
|
|
|
@@ -1190,20 +1327,16 @@ actor G2CfwRenderer {
|
|
|
1190
1327
|
/// Mode 6 full-panel black: seeds the shadow mode-3 frames composite onto.
|
|
1191
1328
|
static func blackPayload() -> Data? {
|
|
1192
1329
|
let pixelCount = frameWidth * frameHeight
|
|
1193
|
-
let rle = rleEncodeSolid(color: 0, pixelCount: pixelCount)
|
|
1194
|
-
guard let compressed = BinaryCodec.zlibRFC1950(rle) else { return nil }
|
|
1195
1330
|
var payload = Data([6])
|
|
1196
|
-
payload.append(
|
|
1331
|
+
payload.append(rleEncodeSolid(color: 0, pixelCount: pixelCount))
|
|
1197
1332
|
return payload
|
|
1198
1333
|
}
|
|
1199
1334
|
|
|
1335
|
+
/// `[6][rle]`: plain RLE, the SID-0xf0 transport deflates the whole message.
|
|
1200
1336
|
static func encodePixels(_ grayscale: Data) -> Data? {
|
|
1201
1337
|
guard grayscale.count == frameWidth * frameHeight else { return nil }
|
|
1202
|
-
let packed = pack4Bpp(grayscale)
|
|
1203
|
-
let rle = rleEncode(packed)
|
|
1204
|
-
guard let compressed = BinaryCodec.zlibRFC1950(rle) else { return nil }
|
|
1205
1338
|
var payload = Data([6])
|
|
1206
|
-
payload.append(
|
|
1339
|
+
payload.append(rleEncode(pack4Bpp(grayscale)))
|
|
1207
1340
|
return payload
|
|
1208
1341
|
}
|
|
1209
1342
|
|