@geastack/core 0.1.23
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/LICENSE +202 -0
- package/README.md +36 -0
- package/app_frame_perf.cpp +77 -0
- package/css.d.ts +1 -0
- package/events.cpp +67 -0
- package/gea_app_entry.cpp +317 -0
- package/gea_sources.mjs +126 -0
- package/gea_sources.sh +36 -0
- package/include/app.h +60 -0
- package/include/apps.h +46 -0
- package/include/audio.h +90 -0
- package/include/ble.h +212 -0
- package/include/bluetooth/hid_profile.h +92 -0
- package/include/camera.h +107 -0
- package/include/canvas.h +290 -0
- package/include/display.h +390 -0
- package/include/display_present.h +869 -0
- package/include/event.h +32 -0
- package/include/events.h +116 -0
- package/include/gea/embedded.h +13 -0
- package/include/gea/reactive_runtime.h +32 -0
- package/include/gea.h +14 -0
- package/include/gea_perf_config.h +96 -0
- package/include/geaos/abi.h +165 -0
- package/include/geolocation.h +126 -0
- package/include/gpio.h +61 -0
- package/include/graphics/font.h +122 -0
- package/include/i2c.h +20 -0
- package/include/image.h +156 -0
- package/include/imu.h +23 -0
- package/include/input.h +33 -0
- package/include/memory.h +52 -0
- package/include/pixel.h +907 -0
- package/include/platform/file_cache.h +18 -0
- package/include/power.h +11 -0
- package/include/runtime.h +83 -0
- package/include/services/app_runner.h +10 -0
- package/include/services/app_state.h +14 -0
- package/include/services/battery_service.h +12 -0
- package/include/services/bluetooth_service.h +13 -0
- package/include/services/diagnostics.h +76 -0
- package/include/services/diagnostics_platform.h +54 -0
- package/include/services/frame_scheduler.h +71 -0
- package/include/services/mirror.h +49 -0
- package/include/services/network_services.h +25 -0
- package/include/services/ota.h +32 -0
- package/include/services/runtime_log.h +11 -0
- package/include/services/storage_service.h +26 -0
- package/include/touch.h +38 -0
- package/include/ui/signal.h +149 -0
- package/include/wifi.h +67 -0
- package/index.d.ts +1940 -0
- package/jsx.d.ts +1 -0
- package/package.json +70 -0
- package/runtime/ble.ts +49 -0
- package/runtime/color.ts +21 -0
- package/runtime/compiler.ts +43 -0
- package/runtime/host.ts +1083 -0
- package/runtime/images.ts +129 -0
- package/runtime/primitives.ts +49 -0
- package/runtime-surface.ts +180 -0
- package/runtime.cpp +431 -0
- package/runtime.ts +34 -0
- package/scripts/build-gea-vite-geatsc.mjs +4514 -0
- package/scripts/dotenv-defines.mjs +98 -0
- package/scripts/gea-bundle-type-hints.mjs +546 -0
- package/scripts/gea-embedded-compat-staging.mjs +17 -0
- package/scripts/gea-embedded-compat-transform.mjs +427 -0
- package/scripts/gea-vite-module-graph-plugin.mjs +578 -0
- package/scripts/generate-gea-embedded-assets.mjs +296 -0
- package/scripts/generate-gea-embedded-fonts.mjs +1252 -0
- package/scripts/generate-installed-app-plan.mjs +55 -0
- package/scripts/native-object-cache.sh +244 -0
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "canvas.h"
|
|
4
|
+
#include "display.h"
|
|
5
|
+
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
#include <array>
|
|
8
|
+
#include <cstddef>
|
|
9
|
+
#include <cstdint>
|
|
10
|
+
#include <cstdlib>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
#include <memory>
|
|
13
|
+
#include <new>
|
|
14
|
+
|
|
15
|
+
#include <string>
|
|
16
|
+
#include <utility>
|
|
17
|
+
#include <vector>
|
|
18
|
+
|
|
19
|
+
#if __has_include("esp_heap_caps.h")
|
|
20
|
+
#include "esp_heap_caps.h"
|
|
21
|
+
#else
|
|
22
|
+
#define GEA_DISPLAY_PRESENT_HAS_ESP_HEAP_CAPS 0
|
|
23
|
+
#define MALLOC_CAP_INTERNAL 0
|
|
24
|
+
#define MALLOC_CAP_8BIT 0
|
|
25
|
+
inline void *heap_caps_malloc(std::size_t size, int)
|
|
26
|
+
{
|
|
27
|
+
return std::malloc(size);
|
|
28
|
+
}
|
|
29
|
+
inline void heap_caps_free(void *ptr)
|
|
30
|
+
{
|
|
31
|
+
std::free(ptr);
|
|
32
|
+
}
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
namespace gea::framework::display_present {
|
|
36
|
+
|
|
37
|
+
struct Rect {
|
|
38
|
+
int x0 = 0;
|
|
39
|
+
int y0 = 0;
|
|
40
|
+
int x1 = -1;
|
|
41
|
+
int y1 = -1;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
inline bool valid(const Rect &rect)
|
|
45
|
+
{
|
|
46
|
+
return rect.x0 <= rect.x1 && rect.y0 <= rect.y1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
inline int area(const Rect &rect)
|
|
50
|
+
{
|
|
51
|
+
if (!valid(rect)) return 0;
|
|
52
|
+
return (rect.x1 - rect.x0 + 1) * (rect.y1 - rect.y0 + 1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
inline Rect unite(const Rect &a, const Rect &b)
|
|
56
|
+
{
|
|
57
|
+
if (!valid(a)) return b;
|
|
58
|
+
if (!valid(b)) return a;
|
|
59
|
+
Rect out = a;
|
|
60
|
+
if (b.x0 < out.x0) out.x0 = b.x0;
|
|
61
|
+
if (b.y0 < out.y0) out.y0 = b.y0;
|
|
62
|
+
if (b.x1 > out.x1) out.x1 = b.x1;
|
|
63
|
+
if (b.y1 > out.y1) out.y1 = b.y1;
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
inline Rect fullScreen(int width, int height)
|
|
68
|
+
{
|
|
69
|
+
return {0, 0, width - 1, height - 1};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
inline Rect clampAndAlign(Rect rect, int width, int height)
|
|
73
|
+
{
|
|
74
|
+
rect.x0 &= ~1;
|
|
75
|
+
rect.y0 &= ~1;
|
|
76
|
+
rect.x1 |= 1;
|
|
77
|
+
rect.y1 |= 1;
|
|
78
|
+
if (rect.x0 < 0) rect.x0 = 0;
|
|
79
|
+
if (rect.y0 < 0) rect.y0 = 0;
|
|
80
|
+
if (rect.x1 >= width) rect.x1 = width - 1;
|
|
81
|
+
if (rect.y1 >= height) rect.y1 = height - 1;
|
|
82
|
+
return rect;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// unique_ptr deleter that frees buffers allocated via heap_caps_malloc.
|
|
86
|
+
// We must NOT use the global operator delete (or std::default_delete) here:
|
|
87
|
+
// the project's overridden operator new (targets/esp32/services/heap.cpp)
|
|
88
|
+
// routes through EspHeapAllocator and prefers PSRAM; freeing a
|
|
89
|
+
// heap_caps_malloc(INTERNAL) buffer through that path corrupts the heap.
|
|
90
|
+
struct HeapCapsFree {
|
|
91
|
+
void operator()(void *p) const noexcept {
|
|
92
|
+
heap_caps_free(p);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
static constexpr int kInlineCircleEntryCapacity = 32;
|
|
97
|
+
|
|
98
|
+
struct Command {
|
|
99
|
+
using CircleEntry = gea::framework::graphics::CircleEntry;
|
|
100
|
+
|
|
101
|
+
gea::platform::display::DisplayPresentCommandType type =
|
|
102
|
+
gea::platform::display::DisplayPresentCommandType::Clear;
|
|
103
|
+
gea::framework::graphics::pixel::native_t color = 0;
|
|
104
|
+
std::uint8_t alpha = 255;
|
|
105
|
+
const gea::framework::graphics::pixel::native_t *pixels = nullptr;
|
|
106
|
+
const std::uint8_t *alphaPixels = nullptr;
|
|
107
|
+
int srcWidth = 0;
|
|
108
|
+
int srcHeight = 0;
|
|
109
|
+
int x = 0;
|
|
110
|
+
int y = 0;
|
|
111
|
+
int w = 0;
|
|
112
|
+
int h = 0;
|
|
113
|
+
int x0 = 0;
|
|
114
|
+
int y0 = 0;
|
|
115
|
+
int x1 = 0;
|
|
116
|
+
int y1 = 0;
|
|
117
|
+
int x2 = 0;
|
|
118
|
+
int y2 = 0;
|
|
119
|
+
int radius = 0;
|
|
120
|
+
float scale = 1.0f;
|
|
121
|
+
int fontFamilyId = -1;
|
|
122
|
+
int fontSizePx = 16;
|
|
123
|
+
std::string text;
|
|
124
|
+
// FillCirclesRgb565: small batches stay inline; larger batches own an
|
|
125
|
+
// INTERNAL-RAM buffer of CircleEntry triples (sorted by y), allocated once
|
|
126
|
+
// in extractFrame. No PSRAM deinterleave copy — the canvas rasterizer reads
|
|
127
|
+
// directly from this storage.
|
|
128
|
+
std::array<CircleEntry, kInlineCircleEntryCapacity> inlineCircles{};
|
|
129
|
+
std::unique_ptr<CircleEntry[], HeapCapsFree> circlesBuffer;
|
|
130
|
+
int circlesCount = 0;
|
|
131
|
+
bool circlesInline = false;
|
|
132
|
+
// FillTrianglesRgb565: same ownership scheme as the circle batch. Entries
|
|
133
|
+
// stay in the producer's painter's (depth) order; the batch's overall row
|
|
134
|
+
// extent is cached in the Command's y0/y1 fields for whole-chunk skips.
|
|
135
|
+
std::unique_ptr<gea::framework::graphics::TriangleEntry[], HeapCapsFree> trianglesBuffer;
|
|
136
|
+
int trianglesCount = 0;
|
|
137
|
+
|
|
138
|
+
const CircleEntry *circleEntries() const
|
|
139
|
+
{
|
|
140
|
+
return circlesInline ? inlineCircles.data() : circlesBuffer.get();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
bool hasCircleEntries() const
|
|
144
|
+
{
|
|
145
|
+
return circlesCount <= 0 || circleEntries() != nullptr;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
struct Frame {
|
|
150
|
+
std::vector<Command> commands;
|
|
151
|
+
|
|
152
|
+
void clear()
|
|
153
|
+
{
|
|
154
|
+
commands.clear();
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
inline Rect rectFromBox(int x, int y, int w, int h)
|
|
159
|
+
{
|
|
160
|
+
if (w <= 0 || h <= 0) return {};
|
|
161
|
+
return {x, y, x + w - 1, y + h - 1};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
inline Rect circleBounds(int x, int y, int radius)
|
|
165
|
+
{
|
|
166
|
+
return {x - radius, y - radius, x + radius, y + radius};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
inline Rect triangleBounds(const Command &command)
|
|
170
|
+
{
|
|
171
|
+
return {
|
|
172
|
+
std::min({command.x0, command.x1, command.x2}),
|
|
173
|
+
std::min({command.y0, command.y1, command.y2}),
|
|
174
|
+
std::max({command.x0, command.x1, command.x2}),
|
|
175
|
+
std::max({command.y0, command.y1, command.y2}),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
inline Rect textBounds(const Command &command)
|
|
180
|
+
{
|
|
181
|
+
const int glyphWidth = std::max(1, static_cast<int>(8.0f * command.scale + 0.5f));
|
|
182
|
+
const int glyphHeight = std::max(1, static_cast<int>(16.0f * command.scale + 0.5f));
|
|
183
|
+
return rectFromBox(command.x, command.y, static_cast<int>(command.text.size()) * glyphWidth, glyphHeight);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
inline Rect commandBounds(const Command &command, int width, int height)
|
|
187
|
+
{
|
|
188
|
+
using Type = gea::platform::display::DisplayPresentCommandType;
|
|
189
|
+
switch (command.type) {
|
|
190
|
+
case Type::Clear:
|
|
191
|
+
return fullScreen(width, height);
|
|
192
|
+
case Type::FillRectRgb565:
|
|
193
|
+
case Type::StrokeRectRgb565:
|
|
194
|
+
return rectFromBox(command.x, command.y, command.w, command.h);
|
|
195
|
+
case Type::FillTriangleRgb565:
|
|
196
|
+
return triangleBounds(command);
|
|
197
|
+
case Type::FillCircleRgb565:
|
|
198
|
+
case Type::StrokeCircleRgb565:
|
|
199
|
+
return circleBounds(command.x, command.y, command.radius);
|
|
200
|
+
case Type::FillCirclesRgb565: {
|
|
201
|
+
Rect bounds{};
|
|
202
|
+
const auto *circles = command.circleEntries();
|
|
203
|
+
const int limit = command.circlesCount;
|
|
204
|
+
if (!circles) return bounds;
|
|
205
|
+
for (int i = 0; i < limit; i++) {
|
|
206
|
+
bounds = unite(bounds, circleBounds(circles[i].x, circles[i].y, command.radius));
|
|
207
|
+
}
|
|
208
|
+
return bounds;
|
|
209
|
+
}
|
|
210
|
+
case Type::FillTrianglesRgb565: {
|
|
211
|
+
Rect bounds{};
|
|
212
|
+
const gea::framework::graphics::TriangleEntry *tris = command.trianglesBuffer.get();
|
|
213
|
+
const int limit = command.trianglesCount;
|
|
214
|
+
if (!tris) return bounds;
|
|
215
|
+
for (int i = 0; i < limit; i++) {
|
|
216
|
+
const auto &t = tris[i];
|
|
217
|
+
const Rect triRect{
|
|
218
|
+
std::min({static_cast<int>(t.x0), static_cast<int>(t.x1), static_cast<int>(t.x2)}),
|
|
219
|
+
static_cast<int>(t.rowY0),
|
|
220
|
+
std::max({static_cast<int>(t.x0), static_cast<int>(t.x1), static_cast<int>(t.x2)}),
|
|
221
|
+
static_cast<int>(t.rowY1),
|
|
222
|
+
};
|
|
223
|
+
bounds = unite(bounds, triRect);
|
|
224
|
+
}
|
|
225
|
+
return bounds;
|
|
226
|
+
}
|
|
227
|
+
case Type::DrawImage:
|
|
228
|
+
return rectFromBox(command.x, command.y, command.srcWidth, command.srcHeight);
|
|
229
|
+
case Type::DrawImageScaled:
|
|
230
|
+
case Type::DrawImageRotated90CW:
|
|
231
|
+
return rectFromBox(command.x, command.y, command.w, command.h);
|
|
232
|
+
case Type::DrawImageTiledX:
|
|
233
|
+
return rectFromBox(command.x, command.y, command.w, command.srcHeight);
|
|
234
|
+
case Type::FillText:
|
|
235
|
+
return textBounds(command);
|
|
236
|
+
}
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
inline void addRect(Rect *rects, int *count, int capacity, Rect rect, int width, int height)
|
|
241
|
+
{
|
|
242
|
+
if (!rects || !count || capacity <= 0) return;
|
|
243
|
+
rect = clampAndAlign(rect, width, height);
|
|
244
|
+
if (!valid(rect)) return;
|
|
245
|
+
|
|
246
|
+
for (int i = 0; i < *count; i++) {
|
|
247
|
+
if (rects[i].x0 <= rect.x1 + 1 && rects[i].x1 + 1 >= rect.x0 &&
|
|
248
|
+
rects[i].y0 <= rect.y1 + 1 && rects[i].y1 + 1 >= rect.y0) {
|
|
249
|
+
rects[i] = unite(rects[i], rect);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (*count < capacity) {
|
|
255
|
+
rects[(*count)++] = rect;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
int best = 0;
|
|
260
|
+
int bestCost = 0x7fffffff;
|
|
261
|
+
for (int i = 0; i < *count; i++) {
|
|
262
|
+
const Rect merged = unite(rects[i], rect);
|
|
263
|
+
const int cost = area(merged) - area(rects[i]);
|
|
264
|
+
if (cost < bestCost) {
|
|
265
|
+
bestCost = cost;
|
|
266
|
+
best = i;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
rects[best] = unite(rects[best], rect);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
inline void addCommandBounds(const Command &command, Rect *rects, int *count, int capacity, int width, int height)
|
|
273
|
+
{
|
|
274
|
+
using Type = gea::platform::display::DisplayPresentCommandType;
|
|
275
|
+
if (command.type == Type::FillCirclesRgb565) {
|
|
276
|
+
const auto *circles = command.circleEntries();
|
|
277
|
+
const int limit = command.circlesCount;
|
|
278
|
+
if (!circles) return;
|
|
279
|
+
for (int i = 0; i < limit; i++) {
|
|
280
|
+
addRect(rects, count, capacity, circleBounds(circles[i].x, circles[i].y, command.radius), width, height);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
addRect(rects, count, capacity, commandBounds(command, width, height), width, height);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
inline bool commandsEqual(const Command &a, const Command &b)
|
|
288
|
+
{
|
|
289
|
+
if (a.type != b.type || a.alpha != b.alpha) return false;
|
|
290
|
+
using Type = gea::platform::display::DisplayPresentCommandType;
|
|
291
|
+
switch (a.type) {
|
|
292
|
+
case Type::Clear:
|
|
293
|
+
return a.color == b.color;
|
|
294
|
+
case Type::FillRectRgb565:
|
|
295
|
+
case Type::StrokeRectRgb565:
|
|
296
|
+
return a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h && a.color == b.color;
|
|
297
|
+
case Type::FillTriangleRgb565:
|
|
298
|
+
return a.x0 == b.x0 && a.y0 == b.y0 && a.x1 == b.x1 && a.y1 == b.y1 &&
|
|
299
|
+
a.x2 == b.x2 && a.y2 == b.y2 && a.color == b.color;
|
|
300
|
+
case Type::FillCircleRgb565:
|
|
301
|
+
case Type::StrokeCircleRgb565:
|
|
302
|
+
return a.x == b.x && a.y == b.y && a.radius == b.radius && a.color == b.color;
|
|
303
|
+
case Type::FillCirclesRgb565: {
|
|
304
|
+
if (a.radius != b.radius || a.circlesCount != b.circlesCount) return false;
|
|
305
|
+
if (a.circlesCount == 0) return true;
|
|
306
|
+
const auto *ap = a.circleEntries();
|
|
307
|
+
const auto *bp = b.circleEntries();
|
|
308
|
+
if (!ap || !bp) return ap == bp;
|
|
309
|
+
return std::memcmp(ap, bp,
|
|
310
|
+
static_cast<std::size_t>(a.circlesCount) *
|
|
311
|
+
sizeof(gea::framework::graphics::CircleEntry)) == 0;
|
|
312
|
+
}
|
|
313
|
+
case Type::FillTrianglesRgb565: {
|
|
314
|
+
if (a.trianglesCount != b.trianglesCount) return false;
|
|
315
|
+
if (a.trianglesCount == 0) return true;
|
|
316
|
+
const auto *ap = a.trianglesBuffer.get();
|
|
317
|
+
const auto *bp = b.trianglesBuffer.get();
|
|
318
|
+
if (!ap || !bp) return ap == bp;
|
|
319
|
+
return std::memcmp(ap, bp,
|
|
320
|
+
static_cast<std::size_t>(a.trianglesCount) *
|
|
321
|
+
sizeof(gea::framework::graphics::TriangleEntry)) == 0;
|
|
322
|
+
}
|
|
323
|
+
case Type::DrawImage:
|
|
324
|
+
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
|
|
325
|
+
a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight && a.x == b.x && a.y == b.y;
|
|
326
|
+
case Type::DrawImageScaled:
|
|
327
|
+
case Type::DrawImageRotated90CW:
|
|
328
|
+
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
|
|
329
|
+
a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight &&
|
|
330
|
+
a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h;
|
|
331
|
+
case Type::DrawImageTiledX:
|
|
332
|
+
return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
|
|
333
|
+
a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight &&
|
|
334
|
+
a.x == b.x && a.y == b.y && a.w == b.w;
|
|
335
|
+
case Type::FillText:
|
|
336
|
+
return a.x == b.x && a.y == b.y && a.color == b.color && a.scale == b.scale &&
|
|
337
|
+
a.fontFamilyId == b.fontFamilyId && a.fontSizePx == b.fontSizePx && a.text == b.text;
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *commands, int commandCount, Frame &frame)
|
|
343
|
+
{
|
|
344
|
+
frame.clear();
|
|
345
|
+
if (!commands || commandCount <= 0) return false;
|
|
346
|
+
frame.commands.reserve(static_cast<std::size_t>(commandCount));
|
|
347
|
+
for (int i = 0; i < commandCount; i++) {
|
|
348
|
+
const auto &command = commands[i];
|
|
349
|
+
Command out;
|
|
350
|
+
out.type = command.type;
|
|
351
|
+
switch (command.type) {
|
|
352
|
+
case gea::platform::display::DisplayPresentCommandType::Clear:
|
|
353
|
+
out.color = command.clear.color;
|
|
354
|
+
break;
|
|
355
|
+
case gea::platform::display::DisplayPresentCommandType::FillRectRgb565:
|
|
356
|
+
out.x = command.fillRectRgb565.x;
|
|
357
|
+
out.y = command.fillRectRgb565.y;
|
|
358
|
+
out.w = command.fillRectRgb565.w;
|
|
359
|
+
out.h = command.fillRectRgb565.h;
|
|
360
|
+
out.color = command.fillRectRgb565.color;
|
|
361
|
+
out.alpha = command.fillRectRgb565.alpha;
|
|
362
|
+
break;
|
|
363
|
+
case gea::platform::display::DisplayPresentCommandType::StrokeRectRgb565:
|
|
364
|
+
out.x = command.strokeRectRgb565.x;
|
|
365
|
+
out.y = command.strokeRectRgb565.y;
|
|
366
|
+
out.w = command.strokeRectRgb565.w;
|
|
367
|
+
out.h = command.strokeRectRgb565.h;
|
|
368
|
+
out.color = command.strokeRectRgb565.color;
|
|
369
|
+
out.alpha = command.strokeRectRgb565.alpha;
|
|
370
|
+
break;
|
|
371
|
+
case gea::platform::display::DisplayPresentCommandType::FillTriangleRgb565:
|
|
372
|
+
out.x0 = command.fillTriangleRgb565.x0;
|
|
373
|
+
out.y0 = command.fillTriangleRgb565.y0;
|
|
374
|
+
out.x1 = command.fillTriangleRgb565.x1;
|
|
375
|
+
out.y1 = command.fillTriangleRgb565.y1;
|
|
376
|
+
out.x2 = command.fillTriangleRgb565.x2;
|
|
377
|
+
out.y2 = command.fillTriangleRgb565.y2;
|
|
378
|
+
out.color = command.fillTriangleRgb565.color;
|
|
379
|
+
out.alpha = command.fillTriangleRgb565.alpha;
|
|
380
|
+
break;
|
|
381
|
+
case gea::platform::display::DisplayPresentCommandType::FillCircleRgb565:
|
|
382
|
+
out.x = command.fillCircleRgb565.x;
|
|
383
|
+
out.y = command.fillCircleRgb565.y;
|
|
384
|
+
out.radius = command.fillCircleRgb565.radius;
|
|
385
|
+
out.color = command.fillCircleRgb565.color;
|
|
386
|
+
out.alpha = command.fillCircleRgb565.alpha;
|
|
387
|
+
break;
|
|
388
|
+
case gea::platform::display::DisplayPresentCommandType::StrokeCircleRgb565:
|
|
389
|
+
out.x = command.strokeCircleRgb565.x;
|
|
390
|
+
out.y = command.strokeCircleRgb565.y;
|
|
391
|
+
out.radius = command.strokeCircleRgb565.radius;
|
|
392
|
+
out.color = command.strokeCircleRgb565.color;
|
|
393
|
+
out.alpha = command.strokeCircleRgb565.alpha;
|
|
394
|
+
break;
|
|
395
|
+
case gea::platform::display::DisplayPresentCommandType::FillCirclesRgb565: {
|
|
396
|
+
const auto &src = command.fillCirclesRgb565;
|
|
397
|
+
if (!src.xs || !src.ys || !src.colors || src.count < 0 || src.radius <= 0) return false;
|
|
398
|
+
out.radius = src.radius;
|
|
399
|
+
out.alpha = src.alpha;
|
|
400
|
+
// Opt 4: small inline storage, otherwise one owned INTERNAL-RAM
|
|
401
|
+
// buffer, sorted in place.
|
|
402
|
+
//
|
|
403
|
+
// The previous design did TWO things this loop now collapses into
|
|
404
|
+
// one: (a) build an interleaved scratch array in INTERNAL RAM and
|
|
405
|
+
// sort it, then (b) scatter-copy the sorted triples into three
|
|
406
|
+
// PSRAM std::vector<uint16_t>'s on the Command. Both the second
|
|
407
|
+
// alloc and the PSRAM stores are gone — extractFrame allocates
|
|
408
|
+
// either an inline Command array or ONE CircleEntry[] in INTERNAL
|
|
409
|
+
// RAM, sorts it, and the canvas rasterizer reads directly from it.
|
|
410
|
+
//
|
|
411
|
+
// MALLOC_CAP_INTERNAL is required: the project's overridden
|
|
412
|
+
// operator new (targets/esp32/services/heap.cpp) prefers PSRAM,
|
|
413
|
+
// and empirically a new PSRAM allocation in this hot path blanks
|
|
414
|
+
// the CO5300 panel — even a 4-byte alloc.
|
|
415
|
+
const int count = src.count;
|
|
416
|
+
using gea::framework::graphics::CircleEntry;
|
|
417
|
+
if (count > 0) {
|
|
418
|
+
CircleEntry *buffer = nullptr;
|
|
419
|
+
if (count <= kInlineCircleEntryCapacity) {
|
|
420
|
+
out.circlesInline = true;
|
|
421
|
+
buffer = out.inlineCircles.data();
|
|
422
|
+
} else {
|
|
423
|
+
buffer = static_cast<CircleEntry *>(
|
|
424
|
+
heap_caps_malloc(sizeof(CircleEntry) * static_cast<std::size_t>(count),
|
|
425
|
+
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
|
426
|
+
if (!buffer) return false;
|
|
427
|
+
}
|
|
428
|
+
bool sortedByBucket = false;
|
|
429
|
+
constexpr int kCircleBucketSortMaxYRange = 2048;
|
|
430
|
+
if (count <= 65535) {
|
|
431
|
+
int minY = static_cast<int>(src.ys[0]);
|
|
432
|
+
int maxY = minY;
|
|
433
|
+
for (int i = 1; i < count; i++) {
|
|
434
|
+
const int y = static_cast<int>(src.ys[i]);
|
|
435
|
+
if (y < minY) minY = y;
|
|
436
|
+
if (y > maxY) maxY = y;
|
|
437
|
+
}
|
|
438
|
+
const int yRange = maxY - minY + 1;
|
|
439
|
+
if (yRange > 0 && yRange <= kCircleBucketSortMaxYRange) {
|
|
440
|
+
std::uint16_t offsets[kCircleBucketSortMaxYRange] = {};
|
|
441
|
+
for (int i = 0; i < count; i++) {
|
|
442
|
+
offsets[static_cast<int>(src.ys[i]) - minY]++;
|
|
443
|
+
}
|
|
444
|
+
std::uint16_t running = 0;
|
|
445
|
+
for (int y = 0; y < yRange; y++) {
|
|
446
|
+
const std::uint16_t n = offsets[y];
|
|
447
|
+
offsets[y] = running;
|
|
448
|
+
running = static_cast<std::uint16_t>(running + n);
|
|
449
|
+
}
|
|
450
|
+
for (int i = 0; i < count; i++) {
|
|
451
|
+
const int bucket = static_cast<int>(src.ys[i]) - minY;
|
|
452
|
+
buffer[offsets[bucket]++] = CircleEntry{src.ys[i], src.xs[i], src.colors[i]};
|
|
453
|
+
}
|
|
454
|
+
sortedByBucket = true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (!sortedByBucket) {
|
|
458
|
+
for (int i = 0; i < count; i++) {
|
|
459
|
+
buffer[i] = CircleEntry{src.ys[i], src.xs[i], src.colors[i]};
|
|
460
|
+
}
|
|
461
|
+
std::sort(buffer, buffer + count,
|
|
462
|
+
[](const CircleEntry &a, const CircleEntry &b) { return a.y < b.y; });
|
|
463
|
+
}
|
|
464
|
+
if (!out.circlesInline) out.circlesBuffer.reset(buffer);
|
|
465
|
+
}
|
|
466
|
+
out.circlesCount = count;
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
case gea::platform::display::DisplayPresentCommandType::FillTrianglesRgb565: {
|
|
470
|
+
// Same INTERNAL-RAM ownership rationale as the circle batch above.
|
|
471
|
+
// Entries arrive pre-packed and depth-ordered from the canvas layer;
|
|
472
|
+
// the copy also caches the batch-wide row extent in the Command's
|
|
473
|
+
// y0/y1 fields for whole-chunk skips in commandRowExtent.
|
|
474
|
+
const auto &src = command.fillTrianglesRgb565;
|
|
475
|
+
if (!src.entries || src.count < 0) return false;
|
|
476
|
+
out.alpha = src.alpha;
|
|
477
|
+
using gea::framework::graphics::TriangleEntry;
|
|
478
|
+
const int count = src.count;
|
|
479
|
+
int batchY0 = 0;
|
|
480
|
+
int batchY1 = -1;
|
|
481
|
+
if (count > 0) {
|
|
482
|
+
TriangleEntry *buffer = static_cast<TriangleEntry *>(
|
|
483
|
+
heap_caps_malloc(sizeof(TriangleEntry) * static_cast<std::size_t>(count),
|
|
484
|
+
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
|
485
|
+
if (!buffer) return false;
|
|
486
|
+
std::memcpy(buffer, src.entries, sizeof(TriangleEntry) * static_cast<std::size_t>(count));
|
|
487
|
+
batchY0 = buffer[0].rowY0;
|
|
488
|
+
batchY1 = buffer[0].rowY1;
|
|
489
|
+
for (int i = 1; i < count; i++) {
|
|
490
|
+
if (buffer[i].rowY0 < batchY0) batchY0 = buffer[i].rowY0;
|
|
491
|
+
if (buffer[i].rowY1 > batchY1) batchY1 = buffer[i].rowY1;
|
|
492
|
+
}
|
|
493
|
+
out.trianglesBuffer.reset(buffer);
|
|
494
|
+
}
|
|
495
|
+
out.trianglesCount = count;
|
|
496
|
+
out.y0 = batchY0;
|
|
497
|
+
out.y1 = batchY1;
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
case gea::platform::display::DisplayPresentCommandType::DrawImage:
|
|
501
|
+
if (!command.drawImage.pixels || command.drawImage.srcWidth <= 0 || command.drawImage.srcHeight <= 0) return false;
|
|
502
|
+
out.pixels = command.drawImage.pixels;
|
|
503
|
+
out.alphaPixels = command.drawImage.alphaPixels;
|
|
504
|
+
out.srcWidth = command.drawImage.srcWidth;
|
|
505
|
+
out.srcHeight = command.drawImage.srcHeight;
|
|
506
|
+
out.x = command.drawImage.x;
|
|
507
|
+
out.y = command.drawImage.y;
|
|
508
|
+
out.alpha = command.drawImage.alpha;
|
|
509
|
+
break;
|
|
510
|
+
case gea::platform::display::DisplayPresentCommandType::DrawImageScaled:
|
|
511
|
+
if (!command.drawImageScaled.pixels || command.drawImageScaled.srcWidth <= 0 ||
|
|
512
|
+
command.drawImageScaled.srcHeight <= 0 || command.drawImageScaled.w <= 0 ||
|
|
513
|
+
command.drawImageScaled.h <= 0)
|
|
514
|
+
return false;
|
|
515
|
+
out.pixels = command.drawImageScaled.pixels;
|
|
516
|
+
out.alphaPixels = command.drawImageScaled.alphaPixels;
|
|
517
|
+
out.srcWidth = command.drawImageScaled.srcWidth;
|
|
518
|
+
out.srcHeight = command.drawImageScaled.srcHeight;
|
|
519
|
+
out.x = command.drawImageScaled.x;
|
|
520
|
+
out.y = command.drawImageScaled.y;
|
|
521
|
+
out.w = command.drawImageScaled.w;
|
|
522
|
+
out.h = command.drawImageScaled.h;
|
|
523
|
+
out.alpha = command.drawImageScaled.alpha;
|
|
524
|
+
out.radius = command.drawImageScaled.radius;
|
|
525
|
+
break;
|
|
526
|
+
case gea::platform::display::DisplayPresentCommandType::DrawImageRotated90CW:
|
|
527
|
+
if (!command.drawImageRotated90CW.pixels || command.drawImageRotated90CW.srcWidth <= 0 ||
|
|
528
|
+
command.drawImageRotated90CW.srcHeight <= 0 || command.drawImageRotated90CW.w <= 0 ||
|
|
529
|
+
command.drawImageRotated90CW.h <= 0)
|
|
530
|
+
return false;
|
|
531
|
+
out.pixels = command.drawImageRotated90CW.pixels;
|
|
532
|
+
out.alphaPixels = command.drawImageRotated90CW.alphaPixels;
|
|
533
|
+
out.srcWidth = command.drawImageRotated90CW.srcWidth;
|
|
534
|
+
out.srcHeight = command.drawImageRotated90CW.srcHeight;
|
|
535
|
+
out.x = command.drawImageRotated90CW.x;
|
|
536
|
+
out.y = command.drawImageRotated90CW.y;
|
|
537
|
+
out.w = command.drawImageRotated90CW.w;
|
|
538
|
+
out.h = command.drawImageRotated90CW.h;
|
|
539
|
+
out.alpha = command.drawImageRotated90CW.alpha;
|
|
540
|
+
break;
|
|
541
|
+
case gea::platform::display::DisplayPresentCommandType::DrawImageTiledX:
|
|
542
|
+
if (!command.drawImageTiledX.pixels || command.drawImageTiledX.srcWidth <= 0 ||
|
|
543
|
+
command.drawImageTiledX.srcHeight <= 0 || command.drawImageTiledX.w <= 0)
|
|
544
|
+
return false;
|
|
545
|
+
out.pixels = command.drawImageTiledX.pixels;
|
|
546
|
+
out.alphaPixels = command.drawImageTiledX.alphaPixels;
|
|
547
|
+
out.srcWidth = command.drawImageTiledX.srcWidth;
|
|
548
|
+
out.srcHeight = command.drawImageTiledX.srcHeight;
|
|
549
|
+
out.x = command.drawImageTiledX.x;
|
|
550
|
+
out.y = command.drawImageTiledX.y;
|
|
551
|
+
out.w = command.drawImageTiledX.w;
|
|
552
|
+
out.alpha = command.drawImageTiledX.alpha;
|
|
553
|
+
break;
|
|
554
|
+
case gea::platform::display::DisplayPresentCommandType::FillText:
|
|
555
|
+
if (!command.fillText.text) return false;
|
|
556
|
+
out.text = command.fillText.text;
|
|
557
|
+
out.x = command.fillText.x;
|
|
558
|
+
out.y = command.fillText.y;
|
|
559
|
+
out.color = command.fillText.color;
|
|
560
|
+
out.scale = command.fillText.scale;
|
|
561
|
+
out.fontFamilyId = command.fillText.fontFamilyId;
|
|
562
|
+
out.fontSizePx = command.fillText.fontSizePx;
|
|
563
|
+
out.alpha = command.fillText.alpha;
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
frame.commands.push_back(std::move(out));
|
|
567
|
+
}
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
inline bool frameHasOpaqueBase(const Frame &frame, int width, int height)
|
|
572
|
+
{
|
|
573
|
+
if (frame.commands.empty()) return false;
|
|
574
|
+
const Command &first = frame.commands.front();
|
|
575
|
+
using Type = gea::platform::display::DisplayPresentCommandType;
|
|
576
|
+
if (first.type == Type::Clear) return true;
|
|
577
|
+
if (first.type != Type::FillRectRgb565 || first.alpha != 255) return false;
|
|
578
|
+
const Rect bounds = rectFromBox(first.x, first.y, first.w, first.h);
|
|
579
|
+
return bounds.x0 <= 0 && bounds.y0 <= 0 && bounds.x1 >= width - 1 && bounds.y1 >= height - 1;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
inline int dirtyRects(const Frame *previous,
|
|
583
|
+
const Frame ¤t,
|
|
584
|
+
Rect *rects,
|
|
585
|
+
int capacity,
|
|
586
|
+
int width,
|
|
587
|
+
int height)
|
|
588
|
+
{
|
|
589
|
+
if (!rects || capacity <= 0 || width <= 0 || height <= 0) return 0;
|
|
590
|
+
if (!frameHasOpaqueBase(current, width, height)) {
|
|
591
|
+
rects[0] = fullScreen(width, height);
|
|
592
|
+
return 1;
|
|
593
|
+
}
|
|
594
|
+
if (!previous || !frameHasOpaqueBase(*previous, width, height)) {
|
|
595
|
+
rects[0] = fullScreen(width, height);
|
|
596
|
+
return 1;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
int count = 0;
|
|
600
|
+
const std::size_t commandCount = std::max(previous->commands.size(), current.commands.size());
|
|
601
|
+
for (std::size_t i = 0; i < commandCount; i++) {
|
|
602
|
+
const Command *oldCommand = i < previous->commands.size() ? &previous->commands[i] : nullptr;
|
|
603
|
+
const Command *newCommand = i < current.commands.size() ? ¤t.commands[i] : nullptr;
|
|
604
|
+
if (oldCommand && newCommand && commandsEqual(*oldCommand, *newCommand)) continue;
|
|
605
|
+
if (oldCommand) addCommandBounds(*oldCommand, rects, &count, capacity, width, height);
|
|
606
|
+
if (newCommand) addCommandBounds(*newCommand, rects, &count, capacity, width, height);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
int totalArea = 0;
|
|
610
|
+
for (int i = 0; i < count; i++) totalArea += area(rects[i]);
|
|
611
|
+
if (totalArea >= width * height) {
|
|
612
|
+
rects[0] = fullScreen(width, height);
|
|
613
|
+
return 1;
|
|
614
|
+
}
|
|
615
|
+
return count;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Cheap vertical extent of a command, for skipping whole flush chunks.
|
|
619
|
+
// Returns false for types without a cheap extent (Clear, FillText, the
|
|
620
|
+
// pre-banded circle batch) — those must be visited by every chunk, and
|
|
621
|
+
// outY0/outY1 are set to the full display range.
|
|
622
|
+
inline bool commandRowExtent(const Command &command, int displayHeight, int &outY0, int &outY1)
|
|
623
|
+
{
|
|
624
|
+
using Type = gea::platform::display::DisplayPresentCommandType;
|
|
625
|
+
switch (command.type) {
|
|
626
|
+
case Type::FillRectRgb565:
|
|
627
|
+
case Type::StrokeRectRgb565:
|
|
628
|
+
outY0 = command.y;
|
|
629
|
+
outY1 = command.y + command.h - 1;
|
|
630
|
+
return true;
|
|
631
|
+
case Type::FillTriangleRgb565: {
|
|
632
|
+
const int lo01 = command.y0 < command.y1 ? command.y0 : command.y1;
|
|
633
|
+
const int hi01 = command.y0 > command.y1 ? command.y0 : command.y1;
|
|
634
|
+
outY0 = lo01 < command.y2 ? lo01 : command.y2;
|
|
635
|
+
outY1 = hi01 > command.y2 ? hi01 : command.y2;
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
case Type::FillTrianglesRgb565:
|
|
639
|
+
// Batch-wide extent cached at extract time; per-entry rejects happen
|
|
640
|
+
// inside the raster loop.
|
|
641
|
+
outY0 = command.y0;
|
|
642
|
+
outY1 = command.y1;
|
|
643
|
+
return true;
|
|
644
|
+
case Type::FillCircleRgb565:
|
|
645
|
+
case Type::StrokeCircleRgb565:
|
|
646
|
+
outY0 = command.y - command.radius;
|
|
647
|
+
outY1 = command.y + command.radius;
|
|
648
|
+
return true;
|
|
649
|
+
case Type::DrawImage:
|
|
650
|
+
case Type::DrawImageTiledX:
|
|
651
|
+
outY0 = command.y;
|
|
652
|
+
outY1 = command.y + command.srcHeight - 1;
|
|
653
|
+
return true;
|
|
654
|
+
case Type::DrawImageScaled:
|
|
655
|
+
case Type::DrawImageRotated90CW:
|
|
656
|
+
outY0 = command.y;
|
|
657
|
+
outY1 = command.y + command.h - 1;
|
|
658
|
+
return true;
|
|
659
|
+
case Type::FillText:
|
|
660
|
+
if (command.fontFamilyId < 0) {
|
|
661
|
+
const Rect bounds = textBounds(command);
|
|
662
|
+
outY0 = bounds.y0;
|
|
663
|
+
outY1 = bounds.y1;
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
break;
|
|
667
|
+
default:
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
outY0 = 0;
|
|
671
|
+
outY1 = displayHeight - 1;
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
inline void rasterCommandRows(gea::framework::graphics::Canvas &canvas,
|
|
676
|
+
gea::framework::graphics::pixel::native_t *buffer,
|
|
677
|
+
int regionX0,
|
|
678
|
+
int regionWidth,
|
|
679
|
+
int row,
|
|
680
|
+
int chunkRows,
|
|
681
|
+
int displayHeight,
|
|
682
|
+
const Command &command)
|
|
683
|
+
{
|
|
684
|
+
if (!buffer) return;
|
|
685
|
+
using Type = gea::platform::display::DisplayPresentCommandType;
|
|
686
|
+
// Row-extent reject: the frame's command list is replayed once per flush
|
|
687
|
+
// chunk, so a command whose vertical extent misses [row, row+chunkRows)
|
|
688
|
+
// must cost O(1) here. Without this, every 16-row chunk dispatched EVERY
|
|
689
|
+
// command into Canvas (bind + primitive-level clipping) — ~6.3k dispatches
|
|
690
|
+
// per frame for a 360-triangle scene, ~30ms/frame on esp32-s3. With it, a
|
|
691
|
+
// triangle spanning ~40 rows is visited by the ~3 chunks it touches.
|
|
692
|
+
// (Callers that replay many chunks should hoist commandRowExtent() out of
|
|
693
|
+
// their chunk loop and skip before calling — this in-function reject is
|
|
694
|
+
// the safety net for one-shot callers.)
|
|
695
|
+
{
|
|
696
|
+
const int chunkY1 = row + chunkRows - 1;
|
|
697
|
+
int cmdY0 = 0;
|
|
698
|
+
int cmdY1 = displayHeight - 1;
|
|
699
|
+
const bool bounded = commandRowExtent(command, displayHeight, cmdY0, cmdY1);
|
|
700
|
+
if (bounded && (cmdY1 < row || cmdY0 > chunkY1)) return;
|
|
701
|
+
}
|
|
702
|
+
if (command.type == Type::Clear) {
|
|
703
|
+
const int pixelCount = regionWidth * chunkRows;
|
|
704
|
+
if (command.color == 0) std::memset(buffer, 0, static_cast<std::size_t>(pixelCount) * sizeof(std::uint16_t));
|
|
705
|
+
else std::fill_n(buffer, pixelCount, command.color);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
canvas.setGlobalAlpha(command.alpha);
|
|
710
|
+
const int ox = regionX0;
|
|
711
|
+
const int oy = row;
|
|
712
|
+
switch (command.type) {
|
|
713
|
+
case Type::Clear:
|
|
714
|
+
break;
|
|
715
|
+
case Type::FillRectRgb565:
|
|
716
|
+
// Present replay fills a fresh flush chunk from scratch, so Canvas::fillRect's
|
|
717
|
+
// persistent-surface optimizations (per-pixel dst-compare to skip unchanged pixels +
|
|
718
|
+
// run-based markDirty) are pure overhead here — every pixel is new and the present
|
|
719
|
+
// already owns the dirty region. Opaque rects (the full-screen background + floor
|
|
720
|
+
// band that cover most of every chunk) take the flat span fill; only translucent
|
|
721
|
+
// rects need the per-pixel blend.
|
|
722
|
+
if (command.alpha == 255)
|
|
723
|
+
canvas.fillRectOpaque(command.x - ox, command.y - oy, command.w, command.h, command.color);
|
|
724
|
+
else
|
|
725
|
+
canvas.fillRect(command.x - ox, command.y - oy, command.w, command.h, command.color);
|
|
726
|
+
break;
|
|
727
|
+
case Type::StrokeRectRgb565:
|
|
728
|
+
canvas.strokeRect(command.x - ox, command.y - oy, command.w, command.h, command.color);
|
|
729
|
+
break;
|
|
730
|
+
case Type::FillTriangleRgb565:
|
|
731
|
+
// Same rationale as the opaque-rect branch above: replay chunks are
|
|
732
|
+
// fresh, so skip the per-pixel dst-compare + markDirty bookkeeping.
|
|
733
|
+
if (command.alpha == 255)
|
|
734
|
+
canvas.fillTriangleOpaque(command.x0 - ox, command.y0 - oy,
|
|
735
|
+
command.x1 - ox, command.y1 - oy,
|
|
736
|
+
command.x2 - ox, command.y2 - oy,
|
|
737
|
+
command.color);
|
|
738
|
+
else
|
|
739
|
+
canvas.fillTriangle(command.x0 - ox, command.y0 - oy,
|
|
740
|
+
command.x1 - ox, command.y1 - oy,
|
|
741
|
+
command.x2 - ox, command.y2 - oy,
|
|
742
|
+
command.color);
|
|
743
|
+
break;
|
|
744
|
+
case Type::FillCircleRgb565:
|
|
745
|
+
canvas.fillCircle(command.x - ox, command.y - oy, command.radius, command.color);
|
|
746
|
+
break;
|
|
747
|
+
case Type::StrokeCircleRgb565:
|
|
748
|
+
canvas.strokeCircle(command.x - ox, command.y - oy, command.radius, command.color);
|
|
749
|
+
break;
|
|
750
|
+
case Type::FillCirclesRgb565: {
|
|
751
|
+
// Hand the whole sorted batch to Canvas's optimized batch entrypoint.
|
|
752
|
+
// It inlines fillCircleNoDirty's hot path and uses [chunkY0, chunkY1]
|
|
753
|
+
// as a world-Y band to skip leading + break trailing in one loop.
|
|
754
|
+
const int chunkY0 = row;
|
|
755
|
+
const int chunkY1 = row + chunkRows - 1;
|
|
756
|
+
canvas.fillCirclesRgb565WorldYSorted(command.circleEntries(),
|
|
757
|
+
command.circlesCount,
|
|
758
|
+
command.radius,
|
|
759
|
+
chunkY0, chunkY1, ox, oy);
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
case Type::FillTrianglesRgb565: {
|
|
763
|
+
// Depth-ordered batch (farthest-first). OPAQUE batches take the span-
|
|
764
|
+
// occlusion path (front-to-back, each pixel written once); it band-rejects
|
|
765
|
+
// per chunk internally. TRANSLUCENT must stay painter's back-to-front.
|
|
766
|
+
const gea::framework::graphics::TriangleEntry *tris = command.trianglesBuffer.get();
|
|
767
|
+
const int count = command.trianglesCount;
|
|
768
|
+
if (!tris) break;
|
|
769
|
+
if (command.alpha == 255) {
|
|
770
|
+
canvas.fillTrianglesOpaqueOccluded(tris, count, ox, oy);
|
|
771
|
+
} else {
|
|
772
|
+
const int chunkY1 = row + chunkRows - 1;
|
|
773
|
+
for (int i = 0; i < count; i++) {
|
|
774
|
+
const auto &t = tris[i];
|
|
775
|
+
if (t.rowY1 < row || t.rowY0 > chunkY1) continue;
|
|
776
|
+
canvas.fillTriangle(t.x0 - ox, t.y0 - oy, t.x1 - ox, t.y1 - oy,
|
|
777
|
+
t.x2 - ox, t.y2 - oy, t.color);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
case Type::DrawImage:
|
|
783
|
+
canvas.drawImage(command.pixels, command.alphaPixels, command.srcWidth, command.srcHeight,
|
|
784
|
+
command.x - ox, command.y - oy);
|
|
785
|
+
break;
|
|
786
|
+
case Type::DrawImageScaled:
|
|
787
|
+
// A corner radius masks the destination box. All four equal to
|
|
788
|
+
// min(w,h)/2 is a circle -- how an OPAQUE sprite gets a round mask with
|
|
789
|
+
// no alpha plane at all, which is both smaller in flash and cheaper to
|
|
790
|
+
// blit than carrying a mask channel.
|
|
791
|
+
if (command.radius > 0) {
|
|
792
|
+
canvas.drawImageRounded(command.pixels, command.alphaPixels, command.srcWidth, command.srcHeight,
|
|
793
|
+
command.x - ox, command.y - oy, command.w, command.h,
|
|
794
|
+
command.radius, command.radius, command.radius, command.radius);
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
// 1:1 "scaled" draws (integer-zoom map tiles) take the unscaled blit:
|
|
798
|
+
// row memcpys instead of per-pixel nearest sampling (~2.5x faster).
|
|
799
|
+
if (command.w == command.srcWidth && command.h == command.srcHeight)
|
|
800
|
+
canvas.drawImage(command.pixels, command.alphaPixels, command.srcWidth, command.srcHeight,
|
|
801
|
+
command.x - ox, command.y - oy);
|
|
802
|
+
else
|
|
803
|
+
canvas.drawImage(command.pixels, command.alphaPixels, command.srcWidth, command.srcHeight,
|
|
804
|
+
command.x - ox, command.y - oy, command.w, command.h);
|
|
805
|
+
break;
|
|
806
|
+
case Type::DrawImageRotated90CW:
|
|
807
|
+
canvas.drawImageRotated90CW(command.pixels, command.alphaPixels, command.srcWidth, command.srcHeight,
|
|
808
|
+
command.x - ox, command.y - oy, command.w, command.h);
|
|
809
|
+
break;
|
|
810
|
+
case Type::DrawImageTiledX:
|
|
811
|
+
canvas.drawImageTiledX(command.pixels, command.alphaPixels, command.srcWidth, command.srcHeight,
|
|
812
|
+
command.x - ox, command.y - oy, command.w);
|
|
813
|
+
break;
|
|
814
|
+
case Type::FillText:
|
|
815
|
+
if (command.fontFamilyId >= 0)
|
|
816
|
+
canvas.drawTextFontFamily(command.text.c_str(), command.x - ox, command.y - oy, command.color,
|
|
817
|
+
command.fontFamilyId, command.fontSizePx);
|
|
818
|
+
else
|
|
819
|
+
canvas.drawText(command.text.c_str(), command.x - ox, command.y - oy, command.color, command.scale);
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
(void)displayHeight;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
inline void rasterFrameRows(gea::framework::graphics::pixel::native_t *buffer,
|
|
826
|
+
int regionX0,
|
|
827
|
+
int regionWidth,
|
|
828
|
+
int row,
|
|
829
|
+
int chunkRows,
|
|
830
|
+
int displayHeight,
|
|
831
|
+
const Frame &frame)
|
|
832
|
+
{
|
|
833
|
+
if (!buffer || regionWidth <= 0 || chunkRows <= 0) return;
|
|
834
|
+
gea::framework::graphics::Canvas canvas;
|
|
835
|
+
canvas.bindPixels(buffer, regionWidth, chunkRows);
|
|
836
|
+
for (const Command &command : frame.commands) {
|
|
837
|
+
rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
inline void rasterFrameRowsStrided(gea::framework::graphics::pixel::native_t *buffer,
|
|
842
|
+
int stridePixels,
|
|
843
|
+
int regionX0,
|
|
844
|
+
int regionWidth,
|
|
845
|
+
int row,
|
|
846
|
+
int chunkRows,
|
|
847
|
+
int displayHeight,
|
|
848
|
+
const Frame &frame)
|
|
849
|
+
{
|
|
850
|
+
if (!buffer || stridePixels < regionWidth || regionWidth <= 0 || chunkRows <= 0) return;
|
|
851
|
+
gea::framework::graphics::Canvas canvas;
|
|
852
|
+
canvas.bindPixels(buffer, regionWidth, chunkRows, stridePixels);
|
|
853
|
+
for (const Command &command : frame.commands) {
|
|
854
|
+
if (command.type == gea::platform::display::DisplayPresentCommandType::Clear) {
|
|
855
|
+
for (int y = 0; y < chunkRows; ++y) {
|
|
856
|
+
gea::framework::graphics::pixel::native_t *dst = buffer + static_cast<std::size_t>(y) * stridePixels;
|
|
857
|
+
if (command.color == 0) {
|
|
858
|
+
std::memset(dst, 0, static_cast<std::size_t>(regionWidth) * sizeof(gea::framework::graphics::pixel::native_t));
|
|
859
|
+
} else {
|
|
860
|
+
std::fill_n(dst, regionWidth, command.color);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
} // namespace gea::framework::display_present
|