Improves ASCII rasterization speed and simplifies API
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -38,9 +38,9 @@ void main() async {
|
|||||||
|
|
||||||
final AsciiRasterizer asciiRasterizer = AsciiRasterizer(isTerminal: true);
|
final AsciiRasterizer asciiRasterizer = AsciiRasterizer(isTerminal: true);
|
||||||
final SixelRasterizer sixelRasterizer = SixelRasterizer();
|
final SixelRasterizer sixelRasterizer = SixelRasterizer();
|
||||||
Rasterizer rasterizer = sixelRasterizer;
|
CliRasterizer rasterizer = sixelRasterizer;
|
||||||
|
|
||||||
FrameBuffer buffer = FrameBuffer(
|
final FrameBuffer initialFrameBuffer = FrameBuffer(
|
||||||
stdout.terminalColumns,
|
stdout.terminalColumns,
|
||||||
stdout.terminalLines,
|
stdout.terminalLines,
|
||||||
);
|
);
|
||||||
@@ -49,6 +49,7 @@ void main() async {
|
|||||||
data: availableGames.values.first,
|
data: availableGames.values.first,
|
||||||
difficulty: Difficulty.medium,
|
difficulty: Difficulty.medium,
|
||||||
startingEpisode: 0,
|
startingEpisode: 0,
|
||||||
|
frameBuffer: initialFrameBuffer,
|
||||||
audio: CliSilentAudio(),
|
audio: CliSilentAudio(),
|
||||||
input: CliInput(),
|
input: CliInput(),
|
||||||
onGameWon: () {
|
onGameWon: () {
|
||||||
@@ -83,24 +84,17 @@ void main() async {
|
|||||||
if (stdout.hasTerminal) {
|
if (stdout.hasTerminal) {
|
||||||
int cols = stdout.terminalColumns;
|
int cols = stdout.terminalColumns;
|
||||||
int rows = stdout.terminalLines;
|
int rows = stdout.terminalLines;
|
||||||
if (!rasterizer.isTerminalSizeSupported(cols, rows)) {
|
if (!rasterizer.prepareTerminalFrame(engine, columns: cols, rows: rows)) {
|
||||||
// Clear the screen and print the warning at the top left
|
// Clear the screen and print the warning at the top left
|
||||||
stdout.write('\x1b[2J\x1b[H');
|
stdout.write('\x1b[2J\x1b[H');
|
||||||
stdout.write('\x1b[31m[ ERROR ] TERMINAL TOO SMALL\x1b[0m\n\n');
|
|
||||||
stdout.write('${rasterizer.terminalSizeRequirement}\n');
|
|
||||||
stdout.write(
|
stdout.write(
|
||||||
'Current size: \x1b[33m${stdout.terminalColumns}x${stdout.terminalLines}\x1b[0m\n\n',
|
rasterizer.buildTerminalSizeWarning(columns: cols, rows: rows),
|
||||||
);
|
);
|
||||||
stdout.write('Please resize your window to resume the game...');
|
|
||||||
|
|
||||||
// Prevent the engine from simulating a massive time jump when resumed
|
// Prevent the engine from simulating a massive time jump when resumed
|
||||||
lastTick = stopwatch.elapsed;
|
lastTick = stopwatch.elapsed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer.width != cols || buffer.height != rows) {
|
|
||||||
buffer = FrameBuffer(cols, rows);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Normal Game Loop
|
// 2. Normal Game Loop
|
||||||
@@ -112,8 +106,6 @@ void main() async {
|
|||||||
stdout.write('\x1b[H');
|
stdout.write('\x1b[H');
|
||||||
|
|
||||||
engine.tick(elapsed);
|
engine.tick(elapsed);
|
||||||
rasterizer.render(engine, buffer);
|
stdout.write(rasterizer.render(engine));
|
||||||
|
|
||||||
stdout.write(rasterizer.finalizeFrame());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
data: widget.data,
|
data: widget.data,
|
||||||
difficulty: widget.difficulty,
|
difficulty: widget.difficulty,
|
||||||
startingEpisode: widget.startingEpisode,
|
startingEpisode: widget.startingEpisode,
|
||||||
|
frameBuffer: FrameBuffer(320, 200),
|
||||||
audio: widget.audio,
|
audio: widget.audio,
|
||||||
input: widget.input,
|
input: widget.input,
|
||||||
onGameWon: () => Navigator.of(context).pop(),
|
onGameWon: () => Navigator.of(context).pop(),
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class ColoredChar {
|
|||||||
int get argb => (0xFF000000) | (r << 16) | (g << 8) | b;
|
int get argb => (0xFF000000) | (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AsciiRasterizer extends Rasterizer {
|
class AsciiRasterizer extends CliRasterizer<dynamic> {
|
||||||
static const double _targetAspectRatio = 4 / 3;
|
static const double _targetAspectRatio = 4 / 3;
|
||||||
static const int _terminalBackdropArgb = 0xFF009688;
|
static const int _terminalBackdropArgb = 0xFF009688;
|
||||||
static const int _minimumTerminalColumns = 80;
|
static const int _minimumTerminalColumns = 80;
|
||||||
@@ -121,14 +121,16 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
|
|
||||||
// Intercept the base render call to initialize our text grid
|
// Intercept the base render call to initialize our text grid
|
||||||
@override
|
@override
|
||||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
dynamic render(WolfEngine engine) {
|
||||||
_engine = engine;
|
_engine = engine;
|
||||||
_screen = List.generate(
|
_screen = List.generate(
|
||||||
buffer.height,
|
engine.frameBuffer.height,
|
||||||
(_) =>
|
(_) => List.filled(
|
||||||
List.filled(buffer.width, ColoredChar(' ', ColorPalette.vga32Bit[0])),
|
engine.frameBuffer.width,
|
||||||
|
ColoredChar(' ', ColorPalette.vga32Bit[0]),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return super.render(engine, buffer);
|
return super.render(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -630,7 +632,7 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String finalizeFrame() {
|
dynamic finalizeFrame() {
|
||||||
if (_engine.player.damageFlash > 0.0) {
|
if (_engine.player.damageFlash > 0.0) {
|
||||||
if (isTerminal) {
|
if (isTerminal) {
|
||||||
_applyDamageFlashToScene();
|
_applyDamageFlashToScene();
|
||||||
@@ -640,8 +642,9 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
if (isTerminal) {
|
if (isTerminal) {
|
||||||
_composeTerminalScene();
|
_composeTerminalScene();
|
||||||
|
return toAnsiString();
|
||||||
}
|
}
|
||||||
return toAnsiString();
|
return _screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- PRIVATE HUD DRAWING HELPERS ---
|
// --- PRIVATE HUD DRAWING HELPERS ---
|
||||||
@@ -787,7 +790,7 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the current frame to a single printable ANSI string
|
/// Converts the current frame to a single printable ANSI string
|
||||||
String toAnsiString() {
|
StringBuffer toAnsiString() {
|
||||||
StringBuffer buffer = StringBuffer();
|
StringBuffer buffer = StringBuffer();
|
||||||
|
|
||||||
int? lastForeground;
|
int? lastForeground;
|
||||||
@@ -828,6 +831,6 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
// Reset the terminal color at the very end
|
// Reset the terminal color at the very end
|
||||||
buffer.write('\x1b[0m');
|
buffer.write('\x1b[0m');
|
||||||
|
|
||||||
return buffer.toString();
|
return buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||||
|
|
||||||
|
/// Shared terminal orchestration for CLI rasterizers.
|
||||||
|
abstract class CliRasterizer<T> extends Rasterizer<T> {
|
||||||
|
/// Resolves the framebuffer dimensions required by this renderer.
|
||||||
|
///
|
||||||
|
/// The default uses the full terminal size.
|
||||||
|
({int width, int height}) terminalFrameBufferSize(int columns, int rows) {
|
||||||
|
return (width: columns, height: rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies terminal-size policy and updates the engine framebuffer.
|
||||||
|
///
|
||||||
|
/// Returns `false` when the terminal is too small for this renderer.
|
||||||
|
bool prepareTerminalFrame(
|
||||||
|
WolfEngine engine, {
|
||||||
|
required int columns,
|
||||||
|
required int rows,
|
||||||
|
}) {
|
||||||
|
if (!isTerminalSizeSupported(columns, rows)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final size = terminalFrameBufferSize(columns, rows);
|
||||||
|
engine.setFrameBuffer(size.width, size.height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the standard terminal size warning shown by the CLI host.
|
||||||
|
String buildTerminalSizeWarning({required int columns, required int rows}) {
|
||||||
|
return '\x1b[31m[ ERROR ] TERMINAL TOO SMALL\x1b[0m\n\n'
|
||||||
|
'$terminalSizeRequirement\n'
|
||||||
|
'Current size: \x1b[33m${columns}x$rows\x1b[0m\n\n'
|
||||||
|
'Please resize your window to resume the game...';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
|||||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||||
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
|
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
|
||||||
|
|
||||||
abstract class Rasterizer {
|
abstract class Rasterizer<T> {
|
||||||
late List<double> zBuffer;
|
late List<double> zBuffer;
|
||||||
late int width;
|
late int width;
|
||||||
late int height;
|
late int height;
|
||||||
@@ -40,9 +40,9 @@ abstract class Rasterizer {
|
|||||||
|
|
||||||
/// The main entry point called by the game loop.
|
/// The main entry point called by the game loop.
|
||||||
/// Orchestrates the mathematical rendering pipeline.
|
/// Orchestrates the mathematical rendering pipeline.
|
||||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
T render(WolfEngine engine) {
|
||||||
width = buffer.width;
|
width = engine.frameBuffer.width;
|
||||||
height = buffer.height;
|
height = engine.frameBuffer.height;
|
||||||
// The 3D view typically takes up the top 80% of the screen
|
// The 3D view typically takes up the top 80% of the screen
|
||||||
viewHeight = (height * 0.8).toInt();
|
viewHeight = (height * 0.8).toInt();
|
||||||
zBuffer = List.filled(projectionWidth, 0.0);
|
zBuffer = List.filled(projectionWidth, 0.0);
|
||||||
@@ -101,7 +101,7 @@ abstract class Rasterizer {
|
|||||||
void drawHud(WolfEngine engine);
|
void drawHud(WolfEngine engine);
|
||||||
|
|
||||||
/// Return the finished frame (e.g., the FrameBuffer itself, or an ASCII list).
|
/// Return the finished frame (e.g., the FrameBuffer itself, or an ASCII list).
|
||||||
dynamic finalizeFrame();
|
T finalizeFrame();
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// SHARED LIGHTING MATH
|
// SHARED LIGHTING MATH
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'dart:typed_data';
|
|||||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||||
|
|
||||||
class SixelRasterizer extends Rasterizer {
|
class SixelRasterizer extends CliRasterizer<String> {
|
||||||
static const double _targetAspectRatio = 4 / 3;
|
static const double _targetAspectRatio = 4 / 3;
|
||||||
static const int _defaultLineHeightPx = 18;
|
static const int _defaultLineHeightPx = 18;
|
||||||
static const double _defaultCellWidthToHeight = 0.55;
|
static const double _defaultCellWidthToHeight = 0.55;
|
||||||
@@ -85,12 +85,18 @@ class SixelRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
String render(WolfEngine engine) {
|
||||||
_engine = engine;
|
_engine = engine;
|
||||||
final FrameBuffer scaledBuffer = _createScaledBuffer(buffer);
|
final FrameBuffer originalBuffer = engine.frameBuffer;
|
||||||
|
final FrameBuffer scaledBuffer = _createScaledBuffer(originalBuffer);
|
||||||
// We only need 8-bit indices for the 256 VGA colors
|
// We only need 8-bit indices for the 256 VGA colors
|
||||||
_screen = Uint8List(scaledBuffer.width * scaledBuffer.height);
|
_screen = Uint8List(scaledBuffer.width * scaledBuffer.height);
|
||||||
return super.render(engine, scaledBuffer);
|
engine.frameBuffer = scaledBuffer;
|
||||||
|
try {
|
||||||
|
return super.render(engine);
|
||||||
|
} finally {
|
||||||
|
engine.frameBuffer = originalBuffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import 'dart:math' as math;
|
|||||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||||
|
|
||||||
class SoftwareRasterizer extends Rasterizer {
|
class SoftwareRasterizer extends Rasterizer<FrameBuffer> {
|
||||||
late FrameBuffer _buffer;
|
late FrameBuffer _buffer;
|
||||||
late WolfEngine _engine;
|
late WolfEngine _engine;
|
||||||
|
|
||||||
// Intercept the base render call to store our references
|
// Intercept the base render call to store our references
|
||||||
@override
|
@override
|
||||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
FrameBuffer render(WolfEngine engine) {
|
||||||
_engine = engine;
|
_engine = engine;
|
||||||
_buffer = buffer;
|
_buffer = engine.frameBuffer;
|
||||||
return super.render(engine, buffer);
|
return super.render(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class WolfEngine {
|
|||||||
required this.onGameWon,
|
required this.onGameWon,
|
||||||
required this.audio,
|
required this.audio,
|
||||||
required this.input,
|
required this.input,
|
||||||
|
required this.frameBuffer,
|
||||||
}) : doorManager = DoorManager(
|
}) : doorManager = DoorManager(
|
||||||
onPlaySound: (sfxId) => audio.playSoundEffect(sfxId),
|
onPlaySound: (sfxId) => audio.playSoundEffect(sfxId),
|
||||||
);
|
);
|
||||||
@@ -48,6 +49,9 @@ class WolfEngine {
|
|||||||
/// Polls and processes raw user input into actionable engine commands.
|
/// Polls and processes raw user input into actionable engine commands.
|
||||||
final Wolf3dInput input;
|
final Wolf3dInput input;
|
||||||
|
|
||||||
|
/// The shared render target used by all renderer frontends.
|
||||||
|
FrameBuffer frameBuffer;
|
||||||
|
|
||||||
/// Handles the detection and movement of secret "Pushwalls".
|
/// Handles the detection and movement of secret "Pushwalls".
|
||||||
final PushwallManager pushwallManager = PushwallManager();
|
final PushwallManager pushwallManager = PushwallManager();
|
||||||
|
|
||||||
@@ -86,6 +90,17 @@ class WolfEngine {
|
|||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replaces the shared framebuffer when dimensions change.
|
||||||
|
void setFrameBuffer(int width, int height) {
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
throw ArgumentError('FrameBuffer dimensions must be greater than zero.');
|
||||||
|
}
|
||||||
|
if (frameBuffer.width == width && frameBuffer.height == height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frameBuffer = FrameBuffer(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
/// The primary heartbeat of the engine.
|
/// The primary heartbeat of the engine.
|
||||||
///
|
///
|
||||||
/// Updates all world subsystems based on the [elapsed] time.
|
/// Updates all world subsystems based on the [elapsed] time.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export 'src/engine/managers/pushwall_manager.dart';
|
|||||||
export 'src/engine/player/player.dart';
|
export 'src/engine/player/player.dart';
|
||||||
export 'src/engine/rasterizer/ascii_rasterizer.dart'
|
export 'src/engine/rasterizer/ascii_rasterizer.dart'
|
||||||
show AsciiRasterizer, ColoredChar;
|
show AsciiRasterizer, ColoredChar;
|
||||||
|
export 'src/engine/rasterizer/cli_rasterizer.dart';
|
||||||
export 'src/engine/rasterizer/rasterizer.dart';
|
export 'src/engine/rasterizer/rasterizer.dart';
|
||||||
export 'src/engine/rasterizer/sixel_rasterizer.dart';
|
export 'src/engine/rasterizer/sixel_rasterizer.dart';
|
||||||
export 'src/engine/rasterizer/software_rasterizer.dart';
|
export 'src/engine/rasterizer/software_rasterizer.dart';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
|
||||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||||
import 'package:wolf_3d_renderer/base_renderer.dart';
|
import 'package:wolf_3d_renderer/base_renderer.dart';
|
||||||
|
|
||||||
@@ -14,19 +13,28 @@ class WolfAsciiRenderer extends BaseWolfRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _WolfAsciiRendererState extends BaseWolfRendererState<WolfAsciiRenderer> {
|
class _WolfAsciiRendererState extends BaseWolfRendererState<WolfAsciiRenderer> {
|
||||||
|
static const int _renderWidth = 160;
|
||||||
|
static const int _renderHeight = 100;
|
||||||
|
|
||||||
List<List<ColoredChar>> _asciiFrame = [];
|
List<List<ColoredChar>> _asciiFrame = [];
|
||||||
final AsciiRasterizer _asciiRasterizer = AsciiRasterizer();
|
final AsciiRasterizer _asciiRasterizer = AsciiRasterizer();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.engine.frameBuffer.width != _renderWidth ||
|
||||||
|
widget.engine.frameBuffer.height != _renderHeight) {
|
||||||
|
widget.engine.setFrameBuffer(_renderWidth, _renderHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color get scaffoldColor => const Color.fromARGB(255, 4, 64, 64);
|
Color get scaffoldColor => const Color.fromARGB(255, 4, 64, 64);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performRender() {
|
void performRender() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_asciiFrame = _asciiRasterizer.render(
|
_asciiFrame = _asciiRasterizer.render(widget.engine);
|
||||||
widget.engine,
|
|
||||||
FrameBuffer(160, 100),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,22 @@ class WolfFlutterRenderer extends BaseWolfRenderer {
|
|||||||
|
|
||||||
class _WolfFlutterRendererState
|
class _WolfFlutterRendererState
|
||||||
extends BaseWolfRendererState<WolfFlutterRenderer> {
|
extends BaseWolfRendererState<WolfFlutterRenderer> {
|
||||||
final FrameBuffer _frameBuffer = FrameBuffer(320, 200);
|
static const int _renderWidth = 320;
|
||||||
|
static const int _renderHeight = 200;
|
||||||
final SoftwareRasterizer _rasterizer = SoftwareRasterizer();
|
final SoftwareRasterizer _rasterizer = SoftwareRasterizer();
|
||||||
|
|
||||||
ui.Image? _renderedFrame;
|
ui.Image? _renderedFrame;
|
||||||
bool _isRendering = false;
|
bool _isRendering = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.engine.frameBuffer.width != _renderWidth ||
|
||||||
|
widget.engine.frameBuffer.height != _renderHeight) {
|
||||||
|
widget.engine.setFrameBuffer(_renderWidth, _renderHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color get scaffoldColor => const Color.fromARGB(255, 4, 64, 64);
|
Color get scaffoldColor => const Color.fromARGB(255, 4, 64, 64);
|
||||||
|
|
||||||
@@ -32,12 +42,13 @@ class _WolfFlutterRendererState
|
|||||||
if (_isRendering) return;
|
if (_isRendering) return;
|
||||||
_isRendering = true;
|
_isRendering = true;
|
||||||
|
|
||||||
_rasterizer.render(widget.engine, _frameBuffer);
|
final FrameBuffer frameBuffer = widget.engine.frameBuffer;
|
||||||
|
_rasterizer.render(widget.engine);
|
||||||
|
|
||||||
ui.decodeImageFromPixels(
|
ui.decodeImageFromPixels(
|
||||||
_frameBuffer.pixels.buffer.asUint8List(),
|
frameBuffer.pixels.buffer.asUint8List(),
|
||||||
_frameBuffer.width,
|
frameBuffer.width,
|
||||||
_frameBuffer.height,
|
frameBuffer.height,
|
||||||
ui.PixelFormat.rgba8888,
|
ui.PixelFormat.rgba8888,
|
||||||
(ui.Image image) {
|
(ui.Image image) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|||||||
Reference in New Issue
Block a user