@geastack/core 0.1.38 → 0.1.40
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/events.cpp +2 -0
- package/gea_app_entry.cpp +2 -2
- package/gea_sources.mjs +4 -3
- package/include/audio.h +11 -1
- package/include/audio_oscillator.h +52 -0
- package/include/camera.h +21 -0
- package/include/captive_dns.h +71 -0
- package/include/display_present.h +88 -33
- package/include/events.h +31 -3
- package/include/gea/embedded.h +2 -0
- package/include/graphics/font.h +19 -0
- package/include/image.h +47 -1
- package/include/input.h +12 -1
- package/include/platform/atomic_file.h +72 -0
- package/include/platform/file_cache.h +6 -3
- package/include/power.h +2 -0
- package/include/services/frame_scheduler.h +6 -0
- package/include/wifi.h +60 -45
- package/index.d.ts +233 -44
- package/package.json +3 -3
- package/runtime/audio-worklet.ts +7 -11
- package/runtime/host.ts +132 -77
- package/runtime/images.ts +5 -0
- package/runtime-surface.ts +1 -0
- package/runtime.cpp +23 -5
- package/scripts/bitmap-font-source.mjs +155 -0
- package/scripts/build-gea-vite-geatsc.mjs +97 -6
- package/scripts/gea-embedded-asset-policy.mjs +38 -0
- package/scripts/gea-embedded-compat-transform.mjs +4 -2
- package/scripts/gea-vite-module-graph-plugin.mjs +64 -15
- package/scripts/generate-gea-embedded-assets.mjs +17 -3
- package/scripts/generate-gea-embedded-fonts.mjs +986 -670
package/events.cpp
CHANGED
package/gea_app_entry.cpp
CHANGED
|
@@ -268,7 +268,7 @@ void Application::frame(int timestampMs)
|
|
|
268
268
|
applicationFramePhaseSet(ApplicationFramePhase::DrainMicrotasks);
|
|
269
269
|
generated::drainMicrotasks();
|
|
270
270
|
applicationFramePhaseSet(ApplicationFramePhase::AnimationFrameCallbacks);
|
|
271
|
-
gea::host::runAnimationFrameCallbacks(
|
|
271
|
+
gea::host::runAnimationFrameCallbacks(timestampMs);
|
|
272
272
|
generated::drainMicrotasks();
|
|
273
273
|
applicationFramePhaseSet(ApplicationFramePhase::Idle);
|
|
274
274
|
#else
|
|
@@ -294,7 +294,7 @@ void Application::frame(int timestampMs)
|
|
|
294
294
|
|
|
295
295
|
startUs = phaseClockUs();
|
|
296
296
|
applicationFramePhaseSet(ApplicationFramePhase::AnimationFrameCallbacks);
|
|
297
|
-
gea::host::runAnimationFrameCallbacks(
|
|
297
|
+
gea::host::runAnimationFrameCallbacks(timestampMs);
|
|
298
298
|
applicationFramePhaseSet(ApplicationFramePhase::CallbackMicrotasks);
|
|
299
299
|
generated::drainMicrotasks();
|
|
300
300
|
recordFramePhase(ApplicationFramePhase::AnimationFrameCallbacks, phaseClockUs() - startUs);
|
package/gea_sources.mjs
CHANGED
|
@@ -59,7 +59,8 @@ export function includeFlags(env = process.env) {
|
|
|
59
59
|
join(elements, 'ui'),
|
|
60
60
|
geaos,
|
|
61
61
|
join(engine, 'vendor/stb'),
|
|
62
|
-
join(engine, 'vendor/AnimatedGIF')
|
|
62
|
+
join(engine, 'vendor/AnimatedGIF'),
|
|
63
|
+
...(env.GEA_COMPILER_RUNTIME ? [env.GEA_COMPILER_RUNTIME] : [])
|
|
63
64
|
].map((dir) => `-I${dir}`)
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -87,8 +88,8 @@ export function cxxSources(env = process.env) {
|
|
|
87
88
|
// @geastack/host — platform layer + services
|
|
88
89
|
...cpp(host, ['input', 'wifi', 'bluetooth', 'geolocation']),
|
|
89
90
|
...cpp(join(host, 'host'), [
|
|
90
|
-
'timers', 'worker', 'input', 'display', 'fetch', 'media', 'rtc', 'websocket', 'http', 'wifi',
|
|
91
|
-
'ble', 'apps', 'audio', 'audio_worklet', 'video', 'mjpeg', 'image', 'touch', 'imu', 'memory', 'camera', 'geolocation',
|
|
91
|
+
'timers', 'worker', 'input', 'device', 'display', 'fetch', 'media', 'rtc', 'websocket', 'http', 'wifi',
|
|
92
|
+
'ble', 'apps', 'audio', 'audio_worklet', 'audio_buffer_runtime', 'video', 'mjpeg', 'image', 'touch', 'imu', 'memory', 'camera', 'geolocation',
|
|
92
93
|
'tile_loader'
|
|
93
94
|
]),
|
|
94
95
|
// framework services (built on the platform abstraction — no target-specific deps)
|
package/include/audio.h
CHANGED
|
@@ -7,8 +7,18 @@
|
|
|
7
7
|
#include <string>
|
|
8
8
|
#include <functional>
|
|
9
9
|
|
|
10
|
+
// Physical capture/mixer clock shared by the board driver and native hosts.
|
|
11
|
+
// AudioContext and network PCM may use other rates through their resamplers.
|
|
12
|
+
#ifndef GEA_AUDIO_DEVICE_SAMPLE_RATE
|
|
13
|
+
#define GEA_AUDIO_DEVICE_SAMPLE_RATE 16000
|
|
14
|
+
#endif
|
|
15
|
+
|
|
10
16
|
namespace gea::platform::audio {
|
|
11
17
|
|
|
18
|
+
inline constexpr int deviceSampleRate = GEA_AUDIO_DEVICE_SAMPLE_RATE;
|
|
19
|
+
static_assert(deviceSampleRate >= 8000 && deviceSampleRate <= 48000,
|
|
20
|
+
"Audio device sample rate must be between 8000 and 48000 Hz");
|
|
21
|
+
|
|
12
22
|
enum class OscillatorType {
|
|
13
23
|
Sine = 0,
|
|
14
24
|
Square = 1,
|
|
@@ -74,7 +84,7 @@ public:
|
|
|
74
84
|
static bool playPcm(const std::int16_t *samples, std::size_t sample_count, int sample_rate, int channels);
|
|
75
85
|
static void stopPlayback();
|
|
76
86
|
static void flushPlayback();
|
|
77
|
-
// Continuous
|
|
87
|
+
// Continuous mono source at deviceSampleRate; zero means the platform could not attach it.
|
|
78
88
|
static std::uint64_t playPcmStream(std::function<std::size_t(std::int16_t*, std::size_t)> pull);
|
|
79
89
|
static void stopPcmStream(std::uint64_t stream);
|
|
80
90
|
static bool pcmStreamSettled(std::uint64_t stream);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <optional>
|
|
6
|
+
|
|
7
|
+
namespace gea::platform::audio {
|
|
8
|
+
|
|
9
|
+
// Worklet render time and the physical output scheduler can have different
|
|
10
|
+
// origins. Translate an explicit context deadline at the host boundary.
|
|
11
|
+
inline double physicalAudioDeadline(double when, double contextNow,
|
|
12
|
+
double physicalNow) {
|
|
13
|
+
return when > 0 ? physicalNow + when - contextNow : when;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
struct FiniteToneTiming {
|
|
17
|
+
int durationMs;
|
|
18
|
+
int delayMs;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// The embedded oscillator backend schedules one finite tone when stop() is
|
|
22
|
+
// first called. It does not support replacing/cancelling an already queued
|
|
23
|
+
// individual voice. Further stops must never submit that voice a second time.
|
|
24
|
+
class FiniteOscillatorSchedule {
|
|
25
|
+
public:
|
|
26
|
+
void start(double when, double now) {
|
|
27
|
+
startTime_ = when > 0 ? when : now;
|
|
28
|
+
started_ = true;
|
|
29
|
+
submitted_ = false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
std::optional<FiniteToneTiming> stop(double when, double now) {
|
|
33
|
+
if (submitted_)
|
|
34
|
+
return std::nullopt;
|
|
35
|
+
submitted_ = true;
|
|
36
|
+
const double start = started_ ? startTime_ : now;
|
|
37
|
+
const double end = std::max(when > 0 ? when : now, start);
|
|
38
|
+
const int duration =
|
|
39
|
+
static_cast<int>(std::min((end - start) * 1000.0 + 0.5, 2500.0));
|
|
40
|
+
if (duration <= 0)
|
|
41
|
+
return std::nullopt;
|
|
42
|
+
return FiniteToneTiming{
|
|
43
|
+
duration, static_cast<int>(std::max(start - now, 0.0) * 1000.0 + 0.5)};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private:
|
|
47
|
+
double startTime_ = 0;
|
|
48
|
+
bool started_ = false;
|
|
49
|
+
bool submitted_ = false;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
} // namespace gea::platform::audio
|
package/include/camera.h
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "pixel.h"
|
|
4
4
|
|
|
5
5
|
#include <cstdint>
|
|
6
|
+
#include <atomic>
|
|
6
7
|
#include <string>
|
|
7
8
|
|
|
8
9
|
namespace gea::platform::camera {
|
|
@@ -20,6 +21,14 @@ struct DeviceInfo {
|
|
|
20
21
|
int sensorHeight = 0;
|
|
21
22
|
};
|
|
22
23
|
|
|
24
|
+
// Optional encoded-frame producer. The device owns capture/encoding on its
|
|
25
|
+
// worker; reading a frame never waits for a sensor or codec on the UI thread.
|
|
26
|
+
class CameraFrameProvider {
|
|
27
|
+
public:
|
|
28
|
+
virtual ~CameraFrameProvider() = default;
|
|
29
|
+
virtual std::string takeFrameDataUrl() = 0;
|
|
30
|
+
};
|
|
31
|
+
|
|
23
32
|
// Static facade — one active camera at a time on the device. The platform
|
|
24
33
|
// layer maintains the live preview buffer and exposes a small surface that
|
|
25
34
|
// the host bridge forwards into.
|
|
@@ -36,6 +45,12 @@ public:
|
|
|
36
45
|
static void close();
|
|
37
46
|
static bool isOpen();
|
|
38
47
|
|
|
48
|
+
static void setFrameProvider(CameraFrameProvider *provider) { frameProvider().store(provider); }
|
|
49
|
+
static std::string captureFrame() {
|
|
50
|
+
auto *provider = frameProvider().load();
|
|
51
|
+
return provider ? provider->takeFrameDataUrl() : std::string();
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
static int width();
|
|
40
55
|
static int height();
|
|
41
56
|
static int orientation();
|
|
@@ -102,6 +117,12 @@ public:
|
|
|
102
117
|
static void setWhiteBalance(const std::string &mode, double temperatureK, double tint);
|
|
103
118
|
static void setFocus(const std::string &mode, double pointX, double pointY);
|
|
104
119
|
static void setTorch(const std::string &mode, double level);
|
|
120
|
+
|
|
121
|
+
private:
|
|
122
|
+
static std::atomic<CameraFrameProvider *> &frameProvider() {
|
|
123
|
+
static std::atomic<CameraFrameProvider *> provider{nullptr};
|
|
124
|
+
return provider;
|
|
125
|
+
}
|
|
105
126
|
};
|
|
106
127
|
|
|
107
128
|
} // namespace gea::platform::camera
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <cstddef>
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
|
|
8
|
+
namespace gea::framework::network {
|
|
9
|
+
|
|
10
|
+
// A bounded DNS responder for AP captive portals. Rebuild the question name
|
|
11
|
+
// to avoid retaining compression pointers into discarded additional records.
|
|
12
|
+
inline std::size_t captiveDnsReply(const std::uint8_t *query,
|
|
13
|
+
std::size_t length,
|
|
14
|
+
const std::uint8_t address[4],
|
|
15
|
+
std::uint8_t *reply, std::size_t capacity) {
|
|
16
|
+
if (length < 12 || length > 512 || capacity < 12 || (query[2] & 0xf8) ||
|
|
17
|
+
query[4] != 0 || query[5] != 1)
|
|
18
|
+
return 0;
|
|
19
|
+
std::memcpy(reply, query, 12);
|
|
20
|
+
reply[2] = 0x80 | (query[2] & 1);
|
|
21
|
+
reply[3] = 0x80;
|
|
22
|
+
std::memset(reply + 6, 0, 6);
|
|
23
|
+
std::size_t source = 12, target = 12, questionEnd = 0, jumps = 0,
|
|
24
|
+
nameBytes = 0;
|
|
25
|
+
for (;;) {
|
|
26
|
+
if (source >= length)
|
|
27
|
+
return 0;
|
|
28
|
+
const auto count = query[source++];
|
|
29
|
+
if ((count & 0xc0) == 0xc0) {
|
|
30
|
+
if (source >= length || ++jumps > 8)
|
|
31
|
+
return 0;
|
|
32
|
+
const auto offset = ((count & 0x3f) << 8) | query[source++];
|
|
33
|
+
if (!questionEnd)
|
|
34
|
+
questionEnd = source;
|
|
35
|
+
if (offset >= length)
|
|
36
|
+
return 0;
|
|
37
|
+
source = offset;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (count > 63 || source + count > length ||
|
|
41
|
+
target + count + 1 > capacity || (nameBytes += count + 1) > 255)
|
|
42
|
+
return 0;
|
|
43
|
+
reply[target++] = count;
|
|
44
|
+
if (!count)
|
|
45
|
+
break;
|
|
46
|
+
std::memcpy(reply + target, query + source, count);
|
|
47
|
+
target += count;
|
|
48
|
+
source += count;
|
|
49
|
+
}
|
|
50
|
+
if (!questionEnd)
|
|
51
|
+
questionEnd = source;
|
|
52
|
+
if (questionEnd + 4 > length || target + 4 > capacity)
|
|
53
|
+
return 0;
|
|
54
|
+
const auto type = (query[questionEnd] << 8) | query[questionEnd + 1];
|
|
55
|
+
const auto klass = (query[questionEnd + 2] << 8) | query[questionEnd + 3];
|
|
56
|
+
std::memcpy(reply + target, query + questionEnd, 4);
|
|
57
|
+
target += 4;
|
|
58
|
+
// AAAA and other record types receive an empty successful answer so clients
|
|
59
|
+
// can issue their IPv4 query; this portal only owns its AP IPv4 address.
|
|
60
|
+
if (klass != 1 || (type != 1 && type != 255))
|
|
61
|
+
return target;
|
|
62
|
+
if (target + 16 > capacity)
|
|
63
|
+
return 0;
|
|
64
|
+
constexpr std::uint8_t answer[] = {0xc0, 0x0c, 0, 1, 0, 1, 0, 0, 0, 30, 0, 4};
|
|
65
|
+
std::memcpy(reply + target, answer, sizeof(answer));
|
|
66
|
+
std::memcpy(reply + target + sizeof(answer), address, 4);
|
|
67
|
+
reply[7] = 1;
|
|
68
|
+
return target + 16;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
} // namespace gea::framework::network
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "canvas.h"
|
|
4
4
|
#include "display.h"
|
|
5
|
+
#include "image.h"
|
|
5
6
|
|
|
6
7
|
#include <algorithm>
|
|
7
8
|
#include <array>
|
|
@@ -96,6 +97,7 @@ struct HeapCapsFree {
|
|
|
96
97
|
static constexpr int kInlineCircleEntryCapacity = 32;
|
|
97
98
|
|
|
98
99
|
struct Command {
|
|
100
|
+
gea::framework::graphics::ImageReference imageReference;
|
|
99
101
|
using CircleEntry = gea::framework::graphics::CircleEntry;
|
|
100
102
|
|
|
101
103
|
gea::platform::display::DisplayPresentCommandType type =
|
|
@@ -148,6 +150,9 @@ struct Command {
|
|
|
148
150
|
|
|
149
151
|
struct Frame {
|
|
150
152
|
std::vector<Command> commands;
|
|
153
|
+
// Embedded canvas batches preserve bitmap drawing semantics: circle painter
|
|
154
|
+
// order and the established triangle edge rasterization remain observable.
|
|
155
|
+
bool bitmapSemantics = false;
|
|
151
156
|
|
|
152
157
|
void clear()
|
|
153
158
|
{
|
|
@@ -284,6 +289,13 @@ inline void addCommandBounds(const Command &command, Rect *rects, int *count, in
|
|
|
284
289
|
addRect(rects, count, capacity, commandBounds(command, width, height), width, height);
|
|
285
290
|
}
|
|
286
291
|
|
|
292
|
+
// An app-registered buffer (native surface, ImageData565, video frame) is
|
|
293
|
+
// rewritten in place, so equal pointers say nothing about equal pixels.
|
|
294
|
+
inline bool imagePixelsStable(const Command &command)
|
|
295
|
+
{
|
|
296
|
+
return !gea::framework::graphics::ImageStore::instance().pixelsMayChange(command.pixels);
|
|
297
|
+
}
|
|
298
|
+
|
|
287
299
|
inline bool commandsEqual(const Command &a, const Command &b)
|
|
288
300
|
{
|
|
289
301
|
if (a.type != b.type || a.alpha != b.alpha) return false;
|
|
@@ -300,36 +312,51 @@ inline bool commandsEqual(const Command &a, const Command &b)
|
|
|
300
312
|
case Type::FillCircleRgb565:
|
|
301
313
|
case Type::StrokeCircleRgb565:
|
|
302
314
|
return a.x == b.x && a.y == b.y && a.radius == b.radius && a.color == b.color;
|
|
303
|
-
case Type::FillCirclesRgb565:
|
|
304
|
-
|
|
305
|
-
if (a.circlesCount
|
|
315
|
+
case Type::FillCirclesRgb565:
|
|
316
|
+
{
|
|
317
|
+
if (a.radius != b.radius || a.circlesCount != b.circlesCount)
|
|
318
|
+
return false;
|
|
319
|
+
if (a.circlesCount == 0)
|
|
320
|
+
return true;
|
|
306
321
|
const auto *ap = a.circleEntries();
|
|
307
322
|
const auto *bp = b.circleEntries();
|
|
308
|
-
if (!ap || !bp)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
323
|
+
if (!ap || !bp)
|
|
324
|
+
return ap == bp;
|
|
325
|
+
for (int i = 0; i < a.circlesCount; ++i)
|
|
326
|
+
if (ap[i].x != bp[i].x || ap[i].y != bp[i].y || ap[i].color != bp[i].color)
|
|
327
|
+
return false;
|
|
328
|
+
return true;
|
|
312
329
|
}
|
|
313
|
-
case Type::FillTrianglesRgb565:
|
|
314
|
-
|
|
315
|
-
if (a.trianglesCount
|
|
330
|
+
case Type::FillTrianglesRgb565:
|
|
331
|
+
{
|
|
332
|
+
if (a.trianglesCount != b.trianglesCount)
|
|
333
|
+
return false;
|
|
334
|
+
if (a.trianglesCount == 0)
|
|
335
|
+
return true;
|
|
316
336
|
const auto *ap = a.trianglesBuffer.get();
|
|
317
337
|
const auto *bp = b.trianglesBuffer.get();
|
|
318
|
-
if (!ap || !bp)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
338
|
+
if (!ap || !bp)
|
|
339
|
+
return ap == bp;
|
|
340
|
+
for (int i = 0; i < a.trianglesCount; ++i)
|
|
341
|
+
{
|
|
342
|
+
const auto &x = ap[i];
|
|
343
|
+
const auto &y = bp[i];
|
|
344
|
+
if (x.x0 != y.x0 || x.y0 != y.y0 || x.x1 != y.x1 || x.y1 != y.y1 || x.x2 != y.x2 ||
|
|
345
|
+
x.y2 != y.y2 || x.color != y.color || x.rowY0 != y.rowY0 || x.rowY1 != y.rowY1)
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
322
349
|
}
|
|
323
350
|
case Type::DrawImage:
|
|
324
|
-
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
|
|
325
|
-
|
|
351
|
+
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels && imagePixelsStable(a) &&
|
|
352
|
+
a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight && a.x == b.x && a.y == b.y;
|
|
326
353
|
case Type::DrawImageScaled:
|
|
327
354
|
case Type::DrawImageRotated90CW:
|
|
328
|
-
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
|
|
355
|
+
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels && imagePixelsStable(a) &&
|
|
329
356
|
a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight &&
|
|
330
357
|
a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h;
|
|
331
358
|
case Type::DrawImageTiledX:
|
|
332
|
-
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
|
|
359
|
+
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels && imagePixelsStable(a) &&
|
|
333
360
|
a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight &&
|
|
334
361
|
a.x == b.x && a.y == b.y && a.w == b.w;
|
|
335
362
|
case Type::FillText:
|
|
@@ -339,8 +366,9 @@ inline bool commandsEqual(const Command &a, const Command &b)
|
|
|
339
366
|
return false;
|
|
340
367
|
}
|
|
341
368
|
|
|
342
|
-
inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *commands, int commandCount, Frame &frame)
|
|
369
|
+
inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *commands, int commandCount, Frame &frame, bool bitmapSemantics = false)
|
|
343
370
|
{
|
|
371
|
+
frame.bitmapSemantics = bitmapSemantics;
|
|
344
372
|
frame.clear();
|
|
345
373
|
if (!commands || commandCount <= 0) return false;
|
|
346
374
|
frame.commands.reserve(static_cast<std::size_t>(commandCount));
|
|
@@ -427,7 +455,7 @@ inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *co
|
|
|
427
455
|
}
|
|
428
456
|
bool sortedByBucket = false;
|
|
429
457
|
constexpr int kCircleBucketSortMaxYRange = 2048;
|
|
430
|
-
if (count <= 65535) {
|
|
458
|
+
if (!bitmapSemantics && count <= 65535) {
|
|
431
459
|
int minY = static_cast<int>(src.ys[0]);
|
|
432
460
|
int maxY = minY;
|
|
433
461
|
for (int i = 1; i < count; i++) {
|
|
@@ -458,8 +486,9 @@ inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *co
|
|
|
458
486
|
for (int i = 0; i < count; i++) {
|
|
459
487
|
buffer[i] = CircleEntry{src.ys[i], src.xs[i], src.colors[i]};
|
|
460
488
|
}
|
|
461
|
-
|
|
462
|
-
|
|
489
|
+
if (!bitmapSemantics)
|
|
490
|
+
std::sort(buffer, buffer + count,
|
|
491
|
+
[](const CircleEntry &a, const CircleEntry &b) { return a.y < b.y; });
|
|
463
492
|
}
|
|
464
493
|
if (!out.circlesInline) out.circlesBuffer.reset(buffer);
|
|
465
494
|
}
|
|
@@ -563,6 +592,8 @@ inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *co
|
|
|
563
592
|
out.alpha = command.fillText.alpha;
|
|
564
593
|
break;
|
|
565
594
|
}
|
|
595
|
+
out.imageReference = gea::framework::graphics::ImageReference(
|
|
596
|
+
gea::framework::graphics::ImageStore::instance().idForPixels(out.pixels));
|
|
566
597
|
frame.commands.push_back(std::move(out));
|
|
567
598
|
}
|
|
568
599
|
return true;
|
|
@@ -574,8 +605,17 @@ inline bool frameHasOpaqueBase(const Frame &frame, int width, int height)
|
|
|
574
605
|
const Command &first = frame.commands.front();
|
|
575
606
|
using Type = gea::platform::display::DisplayPresentCommandType;
|
|
576
607
|
if (first.type == Type::Clear) return true;
|
|
577
|
-
if (first.
|
|
578
|
-
|
|
608
|
+
if (first.alpha != 255) return false;
|
|
609
|
+
Rect bounds;
|
|
610
|
+
if (first.type == Type::FillRectRgb565) {
|
|
611
|
+
bounds = rectFromBox(first.x, first.y, first.w, first.h);
|
|
612
|
+
} else if (first.type == Type::DrawImage && first.alphaPixels == nullptr) {
|
|
613
|
+
// A full-panel opaque image (putImageData of a whole-screen buffer)
|
|
614
|
+
// paints every pixel, the same as a full-panel fill.
|
|
615
|
+
bounds = rectFromBox(first.x, first.y, first.srcWidth, first.srcHeight);
|
|
616
|
+
} else {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
579
619
|
return bounds.x0 <= 0 && bounds.y0 <= 0 && bounds.x1 >= width - 1 && bounds.y1 >= height - 1;
|
|
580
620
|
}
|
|
581
621
|
|
|
@@ -679,7 +719,8 @@ inline void rasterCommandRows(gea::framework::graphics::Canvas &canvas,
|
|
|
679
719
|
int row,
|
|
680
720
|
int chunkRows,
|
|
681
721
|
int displayHeight,
|
|
682
|
-
const Command &command
|
|
722
|
+
const Command &command,
|
|
723
|
+
bool bitmapSemantics = false)
|
|
683
724
|
{
|
|
684
725
|
if (!buffer) return;
|
|
685
726
|
using Type = gea::platform::display::DisplayPresentCommandType;
|
|
@@ -748,6 +789,13 @@ inline void rasterCommandRows(gea::framework::graphics::Canvas &canvas,
|
|
|
748
789
|
canvas.strokeCircle(command.x - ox, command.y - oy, command.radius, command.color);
|
|
749
790
|
break;
|
|
750
791
|
case Type::FillCirclesRgb565: {
|
|
792
|
+
if (bitmapSemantics) {
|
|
793
|
+
const auto *circles = command.circleEntries();
|
|
794
|
+
for (int i = 0; circles && i < command.circlesCount; ++i)
|
|
795
|
+
canvas.fillCircle(int(circles[i].x) - ox, int(circles[i].y) - oy,
|
|
796
|
+
command.radius, circles[i].color);
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
751
799
|
// Hand the whole sorted batch to Canvas's optimized batch entrypoint.
|
|
752
800
|
// It inlines fillCircleNoDirty's hot path and uses [chunkY0, chunkY1]
|
|
753
801
|
// as a world-Y band to skip leading + break trailing in one loop.
|
|
@@ -760,21 +808,28 @@ inline void rasterCommandRows(gea::framework::graphics::Canvas &canvas,
|
|
|
760
808
|
break;
|
|
761
809
|
}
|
|
762
810
|
case Type::FillTrianglesRgb565: {
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
//
|
|
811
|
+
// Full-display opaque 3D batches retain their front-to-back occlusion
|
|
812
|
+
// optimization. Bitmap canvas batches and translucent geometry replay
|
|
813
|
+
// in producer order, rejecting triangles outside this strip first.
|
|
766
814
|
const gea::framework::graphics::TriangleEntry *tris = command.trianglesBuffer.get();
|
|
767
815
|
const int count = command.trianglesCount;
|
|
768
816
|
if (!tris) break;
|
|
769
|
-
if (command.alpha == 255) {
|
|
817
|
+
if (command.alpha == 255 && !bitmapSemantics) {
|
|
770
818
|
canvas.fillTrianglesOpaqueOccluded(tris, count, ox, oy);
|
|
771
819
|
} else {
|
|
820
|
+
// Retained canvas batches preserve producer order. Sparse numeric
|
|
821
|
+
// geometry needs no coverage bitset; opaque spans avoid per-pixel
|
|
822
|
+
// comparisons while sharing the public triangle edge definition.
|
|
772
823
|
const int chunkY1 = row + chunkRows - 1;
|
|
773
824
|
for (int i = 0; i < count; i++) {
|
|
774
825
|
const auto &t = tris[i];
|
|
775
826
|
if (t.rowY1 < row || t.rowY0 > chunkY1) continue;
|
|
776
|
-
|
|
777
|
-
|
|
827
|
+
if (command.alpha == 255)
|
|
828
|
+
canvas.fillTriangleOpaque(t.x0 - ox, t.y0 - oy, t.x1 - ox, t.y1 - oy,
|
|
829
|
+
t.x2 - ox, t.y2 - oy, t.color);
|
|
830
|
+
else
|
|
831
|
+
canvas.fillTriangle(t.x0 - ox, t.y0 - oy, t.x1 - ox, t.y1 - oy,
|
|
832
|
+
t.x2 - ox, t.y2 - oy, t.color);
|
|
778
833
|
}
|
|
779
834
|
}
|
|
780
835
|
break;
|
|
@@ -834,7 +889,7 @@ inline void rasterFrameRows(gea::framework::graphics::pixel::native_t *buffer,
|
|
|
834
889
|
gea::framework::graphics::Canvas canvas;
|
|
835
890
|
canvas.bindPixels(buffer, regionWidth, chunkRows);
|
|
836
891
|
for (const Command &command : frame.commands) {
|
|
837
|
-
rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command);
|
|
892
|
+
rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command, frame.bitmapSemantics);
|
|
838
893
|
}
|
|
839
894
|
}
|
|
840
895
|
|
|
@@ -862,7 +917,7 @@ inline void rasterFrameRowsStrided(gea::framework::graphics::pixel::native_t *bu
|
|
|
862
917
|
}
|
|
863
918
|
continue;
|
|
864
919
|
}
|
|
865
|
-
rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command);
|
|
920
|
+
rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command, frame.bitmapSemantics);
|
|
866
921
|
}
|
|
867
922
|
}
|
|
868
923
|
|
package/include/events.h
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
#include <cstdint>
|
|
6
6
|
#include <functional>
|
|
7
|
+
#include <memory>
|
|
7
8
|
|
|
8
9
|
namespace gea::framework::events {
|
|
9
10
|
|
|
@@ -15,7 +16,8 @@ enum class PointerEventType {
|
|
|
15
16
|
Input,
|
|
16
17
|
KeyDown,
|
|
17
18
|
Rotary,
|
|
18
|
-
Scroll
|
|
19
|
+
Scroll,
|
|
20
|
+
KeyUp
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
enum class EventPhase {
|
|
@@ -51,6 +53,32 @@ struct TouchPoint {
|
|
|
51
53
|
int pageY = 0;
|
|
52
54
|
};
|
|
53
55
|
|
|
56
|
+
// Native callback lowering may copy the event carrier. Its cancellation flags
|
|
57
|
+
// still belong to the same dispatched event, including copies retained later.
|
|
58
|
+
// Allocate shared storage only when a carrier is actually copied.
|
|
59
|
+
class EventControlFlag {
|
|
60
|
+
public:
|
|
61
|
+
EventControlFlag(bool value = false) : initial_(value) {}
|
|
62
|
+
EventControlFlag(const EventControlFlag &other) : shared_(other.share()) {}
|
|
63
|
+
EventControlFlag &operator=(const EventControlFlag &other) {
|
|
64
|
+
if (this != &other) shared_ = other.share();
|
|
65
|
+
return *this;
|
|
66
|
+
}
|
|
67
|
+
EventControlFlag &operator=(bool value) {
|
|
68
|
+
if (shared_) *shared_ = value;
|
|
69
|
+
else initial_ = value;
|
|
70
|
+
return *this;
|
|
71
|
+
}
|
|
72
|
+
operator bool() const { return shared_ ? *shared_ : initial_; }
|
|
73
|
+
private:
|
|
74
|
+
std::shared_ptr<bool> share() const {
|
|
75
|
+
if (!shared_) shared_ = std::make_shared<bool>(initial_);
|
|
76
|
+
return shared_;
|
|
77
|
+
}
|
|
78
|
+
bool initial_ = false;
|
|
79
|
+
mutable std::shared_ptr<bool> shared_;
|
|
80
|
+
};
|
|
81
|
+
|
|
54
82
|
struct PointerEvent {
|
|
55
83
|
PointerEventType type;
|
|
56
84
|
EventTarget target{};
|
|
@@ -73,8 +101,8 @@ struct PointerEvent {
|
|
|
73
101
|
bool primary = true;
|
|
74
102
|
bool bubbles = true;
|
|
75
103
|
bool cancelable = true;
|
|
76
|
-
|
|
77
|
-
|
|
104
|
+
EventControlFlag defaultPrevented;
|
|
105
|
+
EventControlFlag propagationStopped;
|
|
78
106
|
EventPhase eventPhase = EventPhase::None;
|
|
79
107
|
TouchPoint touches[1]{};
|
|
80
108
|
TouchPoint targetTouches[1]{};
|
package/include/gea/embedded.h
CHANGED
package/include/graphics/font.h
CHANGED
|
@@ -13,6 +13,7 @@ struct Glyph {
|
|
|
13
13
|
int advance = 0;
|
|
14
14
|
int bearingX = 0;
|
|
15
15
|
int bearingY = 0;
|
|
16
|
+
int advance16 = -1;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
class Font {
|
|
@@ -25,6 +26,12 @@ public:
|
|
|
25
26
|
virtual int ascender() const = 0;
|
|
26
27
|
virtual bool glyph(int codepoint, Glyph *out) const = 0;
|
|
27
28
|
virtual std::uint8_t coverage(const Glyph &glyph, int row, int col) const = 0;
|
|
29
|
+
virtual int advance(int codepoint, int nextCodepoint = 0) const
|
|
30
|
+
{
|
|
31
|
+
(void)nextCodepoint;
|
|
32
|
+
Glyph value{};
|
|
33
|
+
return glyph(codepoint, &value) ? value.advance : sizePx() / 2;
|
|
34
|
+
}
|
|
28
35
|
};
|
|
29
36
|
|
|
30
37
|
class BitmapFont : public Font {
|
|
@@ -63,6 +70,13 @@ private:
|
|
|
63
70
|
static const std::uint8_t glyphs[kGlyphCount][kHeight];
|
|
64
71
|
};
|
|
65
72
|
|
|
73
|
+
struct FontKerningPair
|
|
74
|
+
{
|
|
75
|
+
int left = 0;
|
|
76
|
+
int right = 0;
|
|
77
|
+
int adjustment16 = 0;
|
|
78
|
+
};
|
|
79
|
+
|
|
66
80
|
struct RasterizedFontData {
|
|
67
81
|
int id = 0;
|
|
68
82
|
int sizePx = 0;
|
|
@@ -80,6 +94,10 @@ struct RasterizedFontData {
|
|
|
80
94
|
// byte-aligned (stride = (atlasWidth + 3) / 4) and the leftmost pixel of a
|
|
81
95
|
// byte sits in the most significant bit pair.
|
|
82
96
|
int atlasBits = 8;
|
|
97
|
+
int kerningCount = 0;
|
|
98
|
+
const FontKerningPair *kerning = nullptr;
|
|
99
|
+
bool strictGlyphLookup = false;
|
|
100
|
+
int fallbackCodepoint = -1;
|
|
83
101
|
};
|
|
84
102
|
|
|
85
103
|
class RasterizedFont final : public Font {
|
|
@@ -93,6 +111,7 @@ public:
|
|
|
93
111
|
int ascender() const final;
|
|
94
112
|
bool glyph(int codepoint, Glyph *out) const final;
|
|
95
113
|
std::uint8_t coverage(const Glyph &glyph, int row, int col) const final;
|
|
114
|
+
int advance(int codepoint, int nextCodepoint = 0) const final;
|
|
96
115
|
const RasterizedFontData *data() const { return data_; }
|
|
97
116
|
|
|
98
117
|
private:
|