mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
Merge branch 'feature/subpixel-antialiasing' into develop
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
# Retained Text Small-Batch Optimization Plan
|
||||
|
||||
Status: proposal; no implementation started.
|
||||
|
||||
Date: 2026-07-25
|
||||
|
||||
## Goal
|
||||
|
||||
Determine whether small retained `Text` objects should submit their already-cached geometry to
|
||||
`GlobalBatchRenderer` instead of issuing independent direct draw calls, and implement that hybrid
|
||||
path only if controlled benchmarks show a meaningful improvement without regressing large text,
|
||||
ordinary grayscale rendering, LCD subpixel rendering, color emoji, or memory use.
|
||||
|
||||
The initial threshold to investigate is 512 rendered glyph quads, but 512 must not become a fixed
|
||||
policy until measurements establish the crossover point. The decision should use rendered geometry
|
||||
size rather than `String::size()` because shaping, whitespace, fallback glyphs, decorations, and
|
||||
outlines break the one-code-point/one-quad assumption.
|
||||
|
||||
In this document, “retained text” means a `Text` object with cached geometry that currently draws
|
||||
its arrays directly. It is not OpenGL instanced rendering (`glDraw*Instanced`). “Static text” means
|
||||
the static `Text::draw()` convenience path that emits glyphs into `GlobalBatchRenderer`.
|
||||
|
||||
## Current tradeoff
|
||||
|
||||
### Static `Text::draw()`
|
||||
|
||||
Advantages:
|
||||
|
||||
- Consecutive compatible strings can remain in one global batch.
|
||||
- Many small labels can become one draw submission instead of one submission per label.
|
||||
- The renderer already flushes when texture, blend state, or LCD coverage mode changes.
|
||||
|
||||
Costs:
|
||||
|
||||
- Glyph traversal and geometry emission happen on every call.
|
||||
- Shaped text may repeat layout work unless another layer caches it.
|
||||
- Vertices, texture coordinates, and colors are copied into the batch every frame.
|
||||
|
||||
### Retained `Text::draw()`
|
||||
|
||||
Advantages:
|
||||
|
||||
- Layout and vertex construction are cached until invalidated.
|
||||
- Large stable text avoids rebuilding and copying all of its geometry each frame.
|
||||
- Per-character colors and mixed render-mode ranges are already represented.
|
||||
|
||||
Costs:
|
||||
|
||||
- It flushes the global batch before drawing.
|
||||
- Every retained `Text` normally creates at least one independent draw submission.
|
||||
- LCD ranges require multiple channel passes; mixed mask/LCD content can create additional ranges.
|
||||
- A UI containing hundreds of short labels can become draw-call bound even though each label has
|
||||
very little geometry.
|
||||
|
||||
The proposed hybrid keeps retained layout/geometry caching but batches small cached geometry at draw
|
||||
time. Its cost is a bounded CPU-side copy into the batch in exchange for fewer flushes and draw
|
||||
calls.
|
||||
|
||||
## Questions the benchmark must answer
|
||||
|
||||
1. At what rendered-quad count does copying cached geometry into the global batch become slower than
|
||||
drawing the retained arrays directly?
|
||||
2. Does that crossover differ materially between OpenGL 2 (the primary renderer), GL3/core, and
|
||||
GLES2?
|
||||
3. How much does LCD rendering move the crossover because each LCD batch flush performs RGB and
|
||||
alpha passes?
|
||||
4. Do many small labels benefit more than a single string of the same total glyph count?
|
||||
5. How much time is actually spent in layout/geometry generation by static `Text::draw()` compared
|
||||
with submission and driver overhead?
|
||||
6. Does batching retained text reduce draw calls in realistic ecode UI scenes, or do intervening
|
||||
textures, clipping changes, and render modes force immediate flushes and erase the benefit?
|
||||
7. Is a fixed threshold sufficient, or should the decision also consider whether the cached text can
|
||||
join the batch currently being built?
|
||||
|
||||
## Candidate solutions
|
||||
|
||||
### Option A: Geometry-count threshold
|
||||
|
||||
For retained text below a configurable/internal threshold, append cached quads and colors to
|
||||
`GlobalBatchRenderer`. Draw larger text through the existing direct path.
|
||||
|
||||
Conceptually:
|
||||
|
||||
```cpp
|
||||
const size_t quadCount = mVertices.size() / GLi->quadVertex();
|
||||
if ( quadCount <= retainedTextBatchThreshold )
|
||||
submitCachedGeometryToBatch();
|
||||
else
|
||||
drawCachedGeometryDirectly();
|
||||
```
|
||||
|
||||
This is the simplest useful experiment. Candidate thresholds should include 0 (always direct), 32,
|
||||
64, 128, 256, 512, 1024, and an always-batched mode. The production value should be an internal
|
||||
constant unless runtime tuning proves necessary.
|
||||
|
||||
### Option B: Threshold plus batch compatibility
|
||||
|
||||
Batch only when the retained geometry can join a compatible pending batch. Compatibility includes:
|
||||
|
||||
- font atlas texture and coordinate type;
|
||||
- blend mode;
|
||||
- clipping/scissor state;
|
||||
- primitive representation;
|
||||
- LCD versus ordinary mask rendering;
|
||||
- active shader and any other renderer state that currently forces a flush.
|
||||
|
||||
If submitting a `Text` would create an otherwise empty batch that must be flushed immediately,
|
||||
direct drawing is likely cheaper. This option should follow Option A only if instrumentation shows
|
||||
that incompatible state transitions frequently defeat the size-only heuristic.
|
||||
|
||||
### Option C: Store render-mode runs instead of one mode per quad
|
||||
|
||||
Retained LCD text currently needs enough metadata to map every quad to its render mode. A compact
|
||||
run list can represent the common cases more efficiently:
|
||||
|
||||
```text
|
||||
first quad | quad count | mode
|
||||
0 | 34 | Subpixel
|
||||
34 | 1 | Color/Mask
|
||||
35 | 37 | Subpixel
|
||||
```
|
||||
|
||||
Uniform LCD text needs one run; all-mask text can keep the existing empty-vector fast path. The run
|
||||
list maps directly to drawing and batch submission. It avoids retaining a mode value per glyph but
|
||||
adds construction and invalidation complexity. Implement it only if memory measurements show the
|
||||
current sparse mode vector is material or if a run API substantially simplifies hybrid submission.
|
||||
|
||||
### Option D: Batch command references without copying geometry
|
||||
|
||||
Teach the renderer to queue references to immutable retained buffers and merge or multi-draw
|
||||
compatible commands later. This could avoid CPU copies, but it introduces lifetime, invalidation,
|
||||
ordering, clipping, and backend complexity. It is not the first implementation candidate. Consider
|
||||
it only if Option A demonstrates that draw-call reduction is valuable but vertex copying prevents a
|
||||
useful crossover.
|
||||
|
||||
## Recommended implementation sequence
|
||||
|
||||
### Phase 1: Instrument the existing paths
|
||||
|
||||
Add benchmark-only or opt-in counters for:
|
||||
|
||||
- `Text` draw calls split by static and retained paths;
|
||||
- rendered quads and vertices;
|
||||
- global batch flush count and reason;
|
||||
- underlying `drawArrays()` calls;
|
||||
- LCD channel-pass draw calls;
|
||||
- texture, shader, clipping, blend, and render-mode transitions;
|
||||
- bytes copied into batch arrays;
|
||||
- retained geometry rebuild count and time.
|
||||
|
||||
Counters must be disabled or compile away in normal production builds unless their measured overhead
|
||||
is negligible. Do not add atomics or logging to the render loop.
|
||||
|
||||
### Phase 2: Build a dedicated text-rendering benchmark
|
||||
|
||||
Add a focused benchmark to `src/benchmarks/` and the existing `eepp-benchmarks` target. It should
|
||||
create one window/context, preload every glyph and atlas page, warm up shader creation, and run all
|
||||
strategies against identical geometry.
|
||||
|
||||
The benchmark must expose three forced policies so results are comparable:
|
||||
|
||||
- `direct`: existing retained draw path;
|
||||
- `batch`: force cached retained geometry through the proposed batch submission path;
|
||||
- `auto-N`: hybrid path with threshold `N`.
|
||||
|
||||
The forcing mechanism should be benchmark-only or a narrow internal testing hook, not a permanent
|
||||
public `Text` API.
|
||||
|
||||
### Phase 3: Implement cached-geometry batch submission
|
||||
|
||||
Add a bulk submission API to `BatchRenderer` rather than calling its per-quad API in a loop if the
|
||||
bulk API can preserve existing array growth and state invariants. It should accept non-owning spans
|
||||
for the duration of the call and copy once into already-reserved batch storage. It must not allocate
|
||||
per glyph.
|
||||
|
||||
Submit retained render-mode runs in original order:
|
||||
|
||||
- mask/decorations through the normal batch mode;
|
||||
- LCD glyph ranges through `setSubpixelText( true )`;
|
||||
- color glyphs through their established normal/color behavior;
|
||||
- outline, shadow, and fill in their existing paint order.
|
||||
|
||||
The path must preserve transforms, per-vertex colors, atlas coordinates, blend mode, clipping, and
|
||||
the current pixel alignment rules. Avoid converting cached geometry into temporary vectors.
|
||||
|
||||
### Phase 4: Tune and select the policy
|
||||
|
||||
Run the complete benchmark matrix on at least the primary OpenGL 2 renderer. Test another
|
||||
programmable backend where available. Select a threshold only when it improves the small-label
|
||||
workloads and does not materially regress large-text workloads.
|
||||
|
||||
Start with a simple fixed threshold. Add compatibility-aware logic only if counters demonstrate a
|
||||
real problem. Prefer the smallest policy that captures most of the measured benefit.
|
||||
|
||||
### Phase 5: Validate in ecode
|
||||
|
||||
Run ecode with its benchmark mode and representative layouts:
|
||||
|
||||
- empty editor with menus, tabs, status bar, and side panels;
|
||||
- a code document with typical visible line lengths;
|
||||
- multiple splits and minimap enabled;
|
||||
- terminal output with frequent updates;
|
||||
- menus and nested context menus containing many short labels;
|
||||
- grayscale and subpixel antialiasing;
|
||||
- color emoji embedded in UI/editor/terminal text.
|
||||
|
||||
Compare frame time and counters against the direct-retained baseline, not only maximum FPS.
|
||||
|
||||
## Benchmark matrix
|
||||
|
||||
### Geometry sizes
|
||||
|
||||
Use rendered glyph-quad counts near likely crossover points:
|
||||
|
||||
- 4, 8, 16, 32, 64, 128, 256, 512, 1024, and 4096 quads.
|
||||
|
||||
Include both one object of size `N` and many objects with the same aggregate glyph count. Examples:
|
||||
|
||||
- 1 × 512 glyphs;
|
||||
- 8 × 64 glyphs;
|
||||
- 32 × 16 glyphs;
|
||||
- 128 × 4 glyphs.
|
||||
|
||||
This distinguishes vertex-volume cost from per-object draw-call cost.
|
||||
|
||||
### Rendering content
|
||||
|
||||
Test each relevant coverage/state pattern:
|
||||
|
||||
- grayscale/mask-only text;
|
||||
- LCD subpixel-only text;
|
||||
- LCD text with occasional color emoji or grayscale fallback glyphs;
|
||||
- alternating render modes as an intentional worst case;
|
||||
- outline and shadow;
|
||||
- underline and strike-through;
|
||||
- per-character colors;
|
||||
- one shared atlas page versus strings spanning multiple atlas pages/fonts.
|
||||
|
||||
### Update patterns
|
||||
|
||||
- Stable retained geometry: draw the same objects for every measured frame.
|
||||
- Position-only movement: cached geometry remains valid but transforms change.
|
||||
- Color-only updates.
|
||||
- One object mutated per frame.
|
||||
- Every object mutated per frame.
|
||||
- Static `Text::draw()` baseline to quantify the full geometry-generation cost.
|
||||
|
||||
### Scene/state patterns
|
||||
|
||||
- All compatible text drawn consecutively.
|
||||
- Text interleaved with rectangles/icons using the same clipping region.
|
||||
- Text interleaved across different font textures.
|
||||
- Frequent clipping/scissor changes, representative of widgets and editor lines.
|
||||
- Empty batch before every label as a worst case for hybrid submission.
|
||||
- Existing compatible pending batch before every label as the best case.
|
||||
|
||||
## Measurement methodology
|
||||
|
||||
- Build and measure an optimized release configuration. Debug/ASan builds remain mandatory for
|
||||
correctness but are not performance evidence.
|
||||
- Disable VSync and frame-rate limits.
|
||||
- Preload fonts, glyphs, atlas pages, and the lazy LCD shader before timing.
|
||||
- Run warm-up frames until allocations and shader/driver initialization stabilize.
|
||||
- Use a fixed number of frames or submissions large enough to exceed timer noise.
|
||||
- Repeat each case several times and report median plus a tail measure such as p95; retain raw
|
||||
samples where practical.
|
||||
- Randomize or alternate policy order to reduce thermal and clock-frequency bias.
|
||||
- Keep window size, pixel density, font, font size, atlas contents, and visible output identical.
|
||||
- Prevent dead-code elimination and ensure submitted work is consumed.
|
||||
- Separate CPU submission time from completed GPU time. Use an explicit synchronization boundary
|
||||
outside the timed inner loop when comparing total frame completion, or GPU timer queries where
|
||||
supported. Do not put `glFinish()` after every individual text draw.
|
||||
- Record hardware, driver, renderer backend, build flags, and commit identifier with results.
|
||||
- Report milliseconds/frame and draw calls in addition to FPS. Very high FPS compresses meaningful
|
||||
differences and can obscure CPU/GPU synchronization effects.
|
||||
|
||||
## Metrics and decision criteria
|
||||
|
||||
Primary metrics:
|
||||
|
||||
- median and p95 CPU frame/submission time;
|
||||
- median completed frame time or GPU time;
|
||||
- GL draw calls per frame;
|
||||
- global batch flushes per frame;
|
||||
- bytes copied into batch storage per frame;
|
||||
- retained geometry rebuilds and allocations.
|
||||
|
||||
Suggested acceptance criteria:
|
||||
|
||||
- At least a repeatable 10% CPU-frame improvement in the many-small-label workload, or a substantial
|
||||
draw-call reduction that produces a measurable ecode improvement.
|
||||
- No more than a 2% regression in representative large stable editor/terminal workloads.
|
||||
- No measurable regression in the ordinary grayscale path when the hybrid path is disabled or the
|
||||
object exceeds the threshold.
|
||||
- No additional per-frame heap allocations after warm-up.
|
||||
- Exact visual equivalence in the existing subpixel golden test and new hybrid-specific golden
|
||||
cases.
|
||||
|
||||
If no threshold meets those criteria, keep the existing direct retained path and remove the
|
||||
experiment rather than retaining unused complexity.
|
||||
|
||||
## Correctness tests
|
||||
|
||||
Extend rendering tests before enabling the automatic policy:
|
||||
|
||||
- Render identical content through forced direct and forced batched-retained paths and compare the
|
||||
resulting images exactly where the backend is deterministic.
|
||||
- Cover text immediately below, at, and above the selected threshold.
|
||||
- Cover direct/static and retained text on both light and dark backgrounds.
|
||||
- Cover first-glyph LCD text to preserve the render-mode bookkeeping regression test.
|
||||
- Cover mixed LCD plus color emoji/fallback glyphs.
|
||||
- Cover mask-only text and verify it never enters the LCD compositor.
|
||||
- Cover outlines, shadows, underline, strike-through, per-character colors, fractional positions,
|
||||
clipping, transforms, and transparent framebuffers.
|
||||
- Verify invalidation after antialiasing changes rebuilds both geometry and texture coordinates.
|
||||
|
||||
The existing `FontRendering.subpixelCoverageCompositesPerChannel` golden is the baseline guard and
|
||||
must remain byte/pixel identical throughout threshold experiments.
|
||||
|
||||
## Performance and allocation constraints
|
||||
|
||||
- The normal mask-only path must not allocate render-mode metadata merely to support LCD text.
|
||||
- Bulk batch submission must reserve geometrically and avoid temporary per-draw vectors.
|
||||
- Do not add `std::function`, callbacks, dynamic dispatch, logging, or per-glyph heap activity to the
|
||||
render loop.
|
||||
- A run-based mode representation should use compact trivially copyable records and merge adjacent
|
||||
equal modes during geometry construction.
|
||||
- Threshold checks must be constant-time and based on already available geometry counts.
|
||||
- Any compatibility inspection must use cached renderer/batch state, not expensive GL state queries.
|
||||
|
||||
## Expected outcome
|
||||
|
||||
The likely useful design is a hybrid: small, compatible retained text copies cached geometry into
|
||||
the global batch, while large retained text continues to draw directly. The exact threshold may be
|
||||
well below 512 quads and may differ for LCD and mask text. Measurements, not the initial estimate,
|
||||
will decide whether one threshold, separate mode-specific thresholds, or no hybrid path is the best
|
||||
production choice.
|
||||
@@ -0,0 +1,388 @@
|
||||
# Subpixel Text Rendering Plan
|
||||
|
||||
Status: implemented after maintainer approval; review fixes completed on 2026-07-25.
|
||||
|
||||
Date: 2026-07-25
|
||||
|
||||
## Goal
|
||||
|
||||
Make `FontAntialiasing::Subpixel` render useful horizontal RGB LCD text in eepp and ecode,
|
||||
using the coverage filter and per-channel compositing model used by lite-xl while preserving
|
||||
eepp's renderer portability, mixed-font behavior, transparent framebuffers, and hot text paths.
|
||||
|
||||
The public option is historically named “subpixel hinting”, but the work here is principally
|
||||
**LCD subpixel antialiasing and compositing**. `FontHinting` continues to control FreeType outline
|
||||
hinting independently.
|
||||
|
||||
## Evidence and current-state analysis
|
||||
|
||||
### The FreeType half is already present
|
||||
|
||||
`FontTrueType::fontSetRenderOptions()` already performs the important rasterization setup:
|
||||
|
||||
- `FontAntialiasing::Subpixel` selects `FT_RENDER_MODE_LCD`.
|
||||
- The LCD filter weights are `{ 0x10, 0x40, 0x70, 0x40, 0x10 }`, exactly the weights used by
|
||||
the supplied lite-xl renderer.
|
||||
- LCD bitmaps are interpreted at one logical pixel per three FreeType bitmap bytes.
|
||||
- Each glyph atlas pixel stores the three LCD coverages in RGB.
|
||||
|
||||
The atlas alpha is currently set to the arithmetic mean of those coverages. Normal eepp texture
|
||||
blending then treats that one alpha value as the coverage for all destination channels. That loses
|
||||
the information LCD rendering needs and is the direct reason the option does not work correctly.
|
||||
|
||||
Repository history confirms this is deliberately unfinished rather than dead code. Commit
|
||||
`7425b77f9` introduced the settings with the note that subpixel support still needed a fragment
|
||||
shader. The ecode menu later exposed that state as “SubPixel (not working)”.
|
||||
|
||||
### What lite-xl does
|
||||
|
||||
The supplied `ren_draw_text()` path has two separable behaviors:
|
||||
|
||||
1. It rasterizes horizontal LCD masks using the same five FreeType filter weights already used by
|
||||
eepp.
|
||||
2. Its software compositor blends each destination color channel with its corresponding LCD
|
||||
coverage:
|
||||
|
||||
```text
|
||||
out[c] = text[c] * text_alpha * coverage[c]
|
||||
+ dst[c] * (1 - text_alpha * coverage[c])
|
||||
```
|
||||
|
||||
lite-xl also caches three glyph masks translated by 0, 1/3, and 2/3 pixel and selects one from the
|
||||
fractional pen position. That is an additional positioning-quality refinement, not the missing
|
||||
compositor itself. eepp currently quantizes some shaped positions and its retained `Text` path does
|
||||
not know the final fractional screen position while building the glyph cache/vertices, so adopting
|
||||
that part requires a larger cache and layout change. It is explicitly separated into a follow-up
|
||||
scope below.
|
||||
|
||||
### A whole-scene framebuffer is not the required mechanism
|
||||
|
||||
`SceneNode` already has an optional framebuffer path (`enableFrameBuffer()`), using a texture-backed
|
||||
RGBA framebuffer. Enabling it does not restore the three coverages after ordinary alpha blending:
|
||||
once RGB coverage has been reduced to the atlas's average alpha, a post-process shader cannot infer
|
||||
the original channel masks.
|
||||
|
||||
A destination-sampling implementation could use ping-pong framebuffers, but reading and writing the
|
||||
same color attachment is not a valid general solution, and ping-pong rendering would add full-screen
|
||||
copies/bandwidth and nested-FBO complexity. It is unnecessary here. The correct ownership boundary
|
||||
is the glyph draw operation while both the LCD mask and destination blend are still available.
|
||||
|
||||
Therefore this plan makes **no change to `SceneNode` and does not force the application framebuffer
|
||||
on**. The result must work equally when a scene framebuffer is enabled for an independent reason.
|
||||
|
||||
### All render paths that must be covered
|
||||
|
||||
`Text` has two materially different pipelines:
|
||||
|
||||
- The static/high-throughput `Text::draw()` path emits glyphs through `GlobalBatchRenderer`. This is
|
||||
ecode's main editor path and batches across calls.
|
||||
- Retained `Text` objects build vertex/color arrays and later draw them directly. They support
|
||||
per-character colors and their own transforms.
|
||||
|
||||
Both paths can mix LCD glyphs, grayscale fallback glyphs, color emoji, effects, and solid atlas
|
||||
quads for underline/strike-through. A global `FontAntialiasing` check is consequently not a safe
|
||||
draw discriminator. The actual rasterized glyph format must travel with the cached glyph/drawable.
|
||||
|
||||
### Scope-level policy issue in ecode startup
|
||||
|
||||
`FontService` is correctly a `ResourceScope`-level policy owner, and changing its hinting or
|
||||
antialiasing clears associated `FontTrueType` caches. ecode's menu updates that service at runtime.
|
||||
|
||||
At startup, however, ecode applies the loaded policy to individually loaded primary fonts without
|
||||
first updating `defaultResourceScope().getFontService()`. System fallback fonts created later can
|
||||
therefore inherit the service's default grayscale policy until the user changes the menu. The
|
||||
implementation needs to establish both loaded policies on the owning service before asynchronous
|
||||
font/fallback loading starts.
|
||||
|
||||
## Proposed architecture
|
||||
|
||||
### 1. Describe what each cached glyph contains
|
||||
|
||||
Add a compact internal render-kind enum, conceptually:
|
||||
|
||||
- `Mask`: monochrome/grayscale coverage and solid decoration texels.
|
||||
- `LCDMask`: three horizontal RGB coverages.
|
||||
- `Color`: BGRA/color emoji or another intrinsically colored glyph.
|
||||
|
||||
Determine it from the actual FreeType bitmap/pixel mode after rendering, not merely from the font's
|
||||
requested policy. Store it on the cached `Glyph`, propagate it through `GlyphDrawable`, and preserve
|
||||
it in text draw ranges. This correctly handles fallback fonts and formats that cannot produce LCD
|
||||
bitmaps.
|
||||
|
||||
This should remain internal rendering metadata; no public API is needed unless implementation shows
|
||||
that an external renderer consumer genuinely needs it.
|
||||
|
||||
### 2. Add a renderer-owned LCD compositor
|
||||
|
||||
The built-in renderer, rather than `Text`, `FontService`, or `ResourceScope`, should own the shader
|
||||
program and cached uniform/attribute locations. GL programs are context/render-pipeline resources,
|
||||
and renderer lifetime already governs the other built-in programs.
|
||||
|
||||
The shader samples the existing atlas RGB mask. To reproduce lite-xl's per-channel equation with
|
||||
arbitrary vertex colors and ordinary OpenGL blending, render each contiguous LCD text range once per
|
||||
destination color channel:
|
||||
|
||||
1. Select the red mask component in the fragment shader and enable only red in `glColorMask()`.
|
||||
2. Repeat for green and blue.
|
||||
3. Use eepp's normal source-over RGB blend factors for every channel pass.
|
||||
4. Perform an alpha-only pass using the existing mean coverage and source-over alpha factors.
|
||||
|
||||
The alpha-only pass is needed because eepp frequently renders into transparent RGBA framebuffers;
|
||||
lite-xl's software surface simply preserves destination alpha. Omitting a meaningful alpha update
|
||||
would make correctly colored text disappear or composite incorrectly when that framebuffer is drawn
|
||||
later. The alpha pass must use eepp's `BlendMode::Alpha()` alpha factors (`One`,
|
||||
`OneMinusSrcAlpha`), not square the source alpha.
|
||||
|
||||
This four-pass strategy is local to LCD ranges, uses no destination texture reads, supports
|
||||
per-character/vertex text colors, and preserves ordering. It costs additional glyph fragments and
|
||||
draw calls, so ranges and state transitions must be coarse, cached, and benchmarked. Normal
|
||||
grayscale and color-glyph paths remain single-pass and unchanged.
|
||||
|
||||
The compositor must:
|
||||
|
||||
- have shader sources for the supported programmable renderer variants (GL2, GL3/core, and GLES2,
|
||||
following the existing renderer conventions);
|
||||
- compile once per renderer/context, cache all locations, and never do string lookup or shader
|
||||
compilation per text draw;
|
||||
- preserve clipping and the existing model-view/projection conventions;
|
||||
- save/restore the prior program, color mask, blend mode/equation, texture state, and batch state;
|
||||
- coexist with externally selected shaders rather than silently replacing unrelated application
|
||||
drawing state;
|
||||
- fail softly and log once if the LCD program is unavailable.
|
||||
|
||||
For shaderless/unsupported contexts, an LCD request must fall back to a neutral grayscale mask
|
||||
(white RGB plus the mean coverage alpha, or grayscale rasterization before upload). It must never
|
||||
fall through to today's colored-mask-with-average-alpha output.
|
||||
|
||||
### 3. Integrate the static batch path without per-glyph overhead
|
||||
|
||||
Extend `BatchRenderer` with an internal text coverage mode (`normal` or `LCD`) and flush only when
|
||||
that mode actually changes. `Text::drawGlyph()` selects the mode from `GlyphDrawable` metadata.
|
||||
|
||||
The batch flush delegates an LCD range to the renderer compositor; the batcher must not own or
|
||||
compile GL programs. Consecutive editor glyphs then stay in one large LCD batch, while transitions
|
||||
to color emoji, grayscale fallback, or decoration quads produce the minimum necessary flushes.
|
||||
|
||||
Shadows and outline glyphs use the glyph's LCD mode. Underline and strike-through atlas quads always
|
||||
use normal scalar-alpha rendering.
|
||||
|
||||
No callback, heap allocation, dynamic cast, or program lookup is permitted per glyph in this hot
|
||||
path.
|
||||
|
||||
### 4. Integrate retained `Text` with compact ordered ranges
|
||||
|
||||
While rebuilding retained geometry, record contiguous `(first vertex, vertex count, render kind)`
|
||||
ranges alongside the existing fill and outline arrays. Draw those ranges in original order:
|
||||
|
||||
- normal masks and colored glyphs use their current single draw;
|
||||
- LCD ranges use the renderer compositor;
|
||||
- decoration ranges remain normal.
|
||||
|
||||
Prefer an inline/small-vector representation because the common case has one range. Do not add a
|
||||
render-mode field to every vertex: that would increase persistent text memory and GPU bandwidth for
|
||||
all text to solve a range-level state problem.
|
||||
|
||||
Preserve existing character-color behavior, emoji whitening rules, clipping, shadows, outlines,
|
||||
underline/strike-through, and fallback texture/page changes.
|
||||
|
||||
### 5. Apply LCD rendering only where the pixel geometry is valid
|
||||
|
||||
This first implementation defines the existing `Subpixel` option as horizontal **RGB** stripes,
|
||||
matching the supplied lite-xl code and FreeType LCD mode. The current API has no RGB/BGR or vertical
|
||||
panel-order selection.
|
||||
|
||||
LCD masks are only valid when their horizontal samples reach physical framebuffer pixels at a 1:1,
|
||||
axis-aligned transform. At each LCD range/batch—not per glyph—check the effective 2D transform. Use
|
||||
the LCD compositor only for unit-scale, non-rotated output; use the neutral average-coverage
|
||||
grayscale path for rotation, non-unit scaling, shear, or otherwise unsuitable transforms.
|
||||
|
||||
This protects retained text transformations and scene/world text. An independently enabled scene
|
||||
FBO remains eligible only when it is finally presented 1:1 without scaling/filtering that would mix
|
||||
the RGB samples.
|
||||
|
||||
BGR/vertical stripe layouts and transformed-output reconstruction are deferred rather than guessed.
|
||||
|
||||
### 6. Correct policy initialization and ecode presentation
|
||||
|
||||
After ecode loads font settings and before it starts asynchronous main/fallback font loading:
|
||||
|
||||
- set the loaded hinting and antialiasing values on the default scope's `FontService`;
|
||||
- keep per-font setup only where it is still needed for ownership/thread timing;
|
||||
- verify local scene-scope fonts and imported/default fonts retain the intended owning-service
|
||||
semantics;
|
||||
- leave runtime policy changes cache-invalidating and immediately visible;
|
||||
- rename the menu item from “SubPixel (not working)” to “SubPixel” only after the renderer path is
|
||||
complete and tested.
|
||||
|
||||
## Expected file areas
|
||||
|
||||
Exact signatures should follow local conventions discovered during implementation, but the expected
|
||||
touch points are:
|
||||
|
||||
- `include/eepp/graphics/font.hpp` and/or internal font/glyph headers: glyph render-kind metadata.
|
||||
- `src/eepp/graphics/fonttruetype.cpp`: derive the actual kind and provide neutral fallback data.
|
||||
- `include/eepp/graphics/glyphdrawable.hpp`, `src/eepp/graphics/glyphdrawable.cpp`: propagate kind.
|
||||
- `include/eepp/graphics/batchrenderer.hpp`, `src/eepp/graphics/batchrenderer.cpp`: range mode and
|
||||
transition flushes.
|
||||
- renderer headers/implementations and built-in shader source area: context-owned LCD compositor,
|
||||
capability detection, and full state restoration.
|
||||
- `include/eepp/graphics/text.hpp`, `src/eepp/graphics/text.cpp`: both static and retained paths,
|
||||
ordered mode ranges, effects, and transform eligibility.
|
||||
- ecode application/font setup and `src/tools/ecode/settingsmenu.cpp`: service initialization and
|
||||
final menu label.
|
||||
- `src/tests/unit_tests/fontrendering_tests.cpp`: focused coverage and regression tests.
|
||||
|
||||
`src/eepp/scene/scenenode.cpp` is deliberately not an expected implementation file.
|
||||
|
||||
## Implementation sequence
|
||||
|
||||
### Phase A — establish a measurable baseline
|
||||
|
||||
1. Add a small diagnostic/test scene that renders the same colored edge onto opaque and transparent
|
||||
contrasting backgrounds using grayscale and current subpixel policies.
|
||||
2. Record baseline pixels and batch/draw counts for the static editor-like path and retained path.
|
||||
3. Confirm active test renderers and framebuffer formats so shader variants and alpha behavior are
|
||||
exercised deliberately.
|
||||
|
||||
### Phase B — metadata and safe fallback
|
||||
|
||||
1. Introduce glyph render-kind metadata derived from the actual rasterized bitmap.
|
||||
2. Propagate it through `GlyphDrawable` without changing layout metrics or cache keys unnecessarily.
|
||||
3. Make unsupported LCD compositing neutral and grayscale instead of colored.
|
||||
4. Add unit coverage for bitmap-mode classification and policy-driven cache invalidation.
|
||||
|
||||
### Phase C — renderer compositor
|
||||
|
||||
1. Add and validate built-in shader variants.
|
||||
2. Add the RGB channel passes and alpha-only pass with scoped state restoration.
|
||||
3. Add transform/capability gating and the grayscale fallback path.
|
||||
4. Exercise opaque and transparent targets before wiring the main editor path.
|
||||
|
||||
### Phase D — both `Text` paths
|
||||
|
||||
1. Add mode-aware static batching and transition flushes.
|
||||
2. Add compact ordered ranges to retained fill/outline geometry.
|
||||
3. Audit shadows, outlines, decorations, fallback texture pages, color emoji, clipping, and
|
||||
per-character colors.
|
||||
4. Compare both paths pixel-for-pixel where their geometry is otherwise identical.
|
||||
|
||||
### Phase E — service and ecode integration
|
||||
|
||||
1. Initialize the default `FontService` policy before font work starts.
|
||||
2. Verify live option changes and cache rebuilds.
|
||||
3. Remove the “not working” suffix.
|
||||
4. Manually inspect editor text on dark/light themes and layered/transparent UI surfaces.
|
||||
|
||||
### Phase F — performance and correctness validation
|
||||
|
||||
1. Ensure LCD text is range-batched and no new allocation occurs per glyph or per frame.
|
||||
2. Compare frame time, draw calls, flush count, atlas memory, and glyph rebuild behavior in a large
|
||||
syntax-highlighted ecode document.
|
||||
3. If four-pass LCD ranges regress the editor materially, optimize range coalescing/state caching
|
||||
before considering a destination-sampling/FBO design.
|
||||
4. Run formatting, the focused unit tests, the complete suite, and an ASan/debug build according to
|
||||
the project rules.
|
||||
|
||||
## Test plan and acceptance criteria
|
||||
|
||||
### Automated rendering tests
|
||||
|
||||
- Render colored LCD text over a non-neutral opaque background and inspect edge pixels. Each output
|
||||
channel must follow its own atlas coverage rather than the mean coverage.
|
||||
- Render the same case through static `Text::draw()` and retained `Text`; tolerate only documented
|
||||
geometry differences.
|
||||
- Render into a transparent RGBA framebuffer, composite that texture onto another background, and
|
||||
verify text remains visible with correct alpha and RGB behavior.
|
||||
- Exercise LCD text adjacent to grayscale/system fallback glyphs, color emoji, underline,
|
||||
strike-through, shadow, and outline without mode leakage or tinting.
|
||||
- Exercise per-character colors and syntax-style runs in a single batch.
|
||||
- Change `FontService` between grayscale and subpixel at runtime and verify cache invalidation,
|
||||
render-kind changes, and stable metrics.
|
||||
- Verify rotated/scaled text and unsupported shader contexts take the neutral grayscale fallback.
|
||||
- Compare scene-FBO off/on at a 1:1 presentation within a small pixel tolerance; enabling the FBO
|
||||
must not be a prerequisite.
|
||||
- Compile/link every applicable built-in shader variant covered by the test environment.
|
||||
|
||||
### Manual ecode checks
|
||||
|
||||
- Toggle None, Grayscale, and SubPixel live and restart with each persisted selection.
|
||||
- Inspect small editor fonts, syntax colors, selection/search overlays, terminal text, popups, light
|
||||
and dark themes, fallback scripts, and emoji.
|
||||
- Check secondary windows and UI opacity/layering paths.
|
||||
- Check normal and HiDPI configurations, and verify non-1:1 transforms fall back cleanly rather than
|
||||
showing colored fringes in the wrong geometry.
|
||||
|
||||
### Performance acceptance
|
||||
|
||||
- No shader construction, uniform lookup, heap allocation, or glyph-cache lookup added per glyph
|
||||
beyond the existing lookup.
|
||||
- Ordinary grayscale/color text retains its current one-pass path and batching behavior.
|
||||
- LCD draw calls scale with contiguous batches/ranges, not with glyph count.
|
||||
- Editor frame-time and batch counters are documented before/after; a material regression blocks
|
||||
completion until understood and reduced.
|
||||
|
||||
### Project validation commands for the implementation phase
|
||||
|
||||
Follow `.agent/rules/build-project.md` and `.agent/rules/unit-tests.md` exactly:
|
||||
|
||||
1. Regenerate the Linux project with debug symbols and ASan, retaining the current graphics backend
|
||||
and using mold when available.
|
||||
2. Format only changed C/C++ files with the repository format.
|
||||
3. Build with `make -C make/linux -j$(nproc)`.
|
||||
4. Run focused `FontRendering.Subpixel*` tests through `projects/scripts/xvfb-run-eepp`.
|
||||
5. Run the full test binary through the same wrapper.
|
||||
6. Run `git diff --check` and inspect the complete diff for accidental public API/ABI or unrelated
|
||||
changes.
|
||||
|
||||
## Deliberately deferred work
|
||||
|
||||
### Three fractional glyph phases
|
||||
|
||||
lite-xl caches three horizontally translated LCD bitmaps for every glyph and selects a phase from the
|
||||
fractional pen position. Adding this in the initial patch would:
|
||||
|
||||
- multiply LCD bitmap/cache variants by three;
|
||||
- require phase-aware glyph keys and drawables;
|
||||
- require retaining fractional shaped positions now truncated in part of `Text::draw()`;
|
||||
- require the retained path to select or rebuild phases using the eventual screen-space origin;
|
||||
- complicate transformed and fallback text behavior.
|
||||
|
||||
Recommendation: land and measure the correct LCD compositor first. Then add the three-phase cache as
|
||||
a separately reviewed quality improvement using the same pixel tests and memory/performance
|
||||
benchmarks. This still follows lite-xl's essential visual algorithm in the first patch: identical
|
||||
filter weights and independent per-channel blending.
|
||||
|
||||
### Other deferred extensions
|
||||
|
||||
- Configurable RGB versus BGR panel order and vertical subpixel layouts.
|
||||
- Gamma-linearized coverage/compositing; lite-xl's cited implementation blends byte-space values, so
|
||||
changing color space would no longer be a direct match.
|
||||
- Enabling the scene framebuffer for unrelated post-processing.
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
- **Four-pass LCD cost:** batch contiguous ranges, cache all state/program data, benchmark ecode's
|
||||
actual editor workload, and keep all non-LCD text on the original path.
|
||||
- **GL state leakage:** use a single renderer-owned entry point with explicit scoped restoration and
|
||||
regression tests that draw other primitives immediately before/after LCD text.
|
||||
- **Mixed atlas content:** classify actual glyph bitmap formats and ordered ranges; never infer the
|
||||
whole draw mode solely from the font setting.
|
||||
- **Transparent FBO alpha:** retain the explicit alpha-only coverage pass and test the final
|
||||
framebuffer composition, not just its RGB attachment.
|
||||
- **Transforms/panel assumptions:** gate LCD at range granularity and fall back neutrally when the
|
||||
physical pixel mapping is unsuitable.
|
||||
- **Startup fallback mismatch:** initialize the owning `FontService` before asynchronous font loads.
|
||||
- **Legacy renderer support:** compile per-renderer variants and provide a neutral non-LCD fallback;
|
||||
do not make subpixel support a requirement for eepp to render text.
|
||||
|
||||
## Approval decisions
|
||||
|
||||
The recommended initial implementation assumes:
|
||||
|
||||
1. Direct per-glyph-range compositing; no forced `SceneNode` framebuffer.
|
||||
2. Correct RGB and alpha output via three color-channel passes plus one alpha-only pass.
|
||||
3. Horizontal RGB order only, with neutral grayscale fallback for unsuitable transforms/contexts.
|
||||
4. Matching lite-xl's existing filter weights and per-channel blend equation now.
|
||||
5. Deferring lite-xl's three fractional-position glyph phases to a follow-up patch.
|
||||
|
||||
Implementation should begin only after these scope decisions are approved or revised.
|
||||
@@ -443,40 +443,40 @@ jobs:
|
||||
files: |
|
||||
projects/freebsd/ecode/ecode-freebsd-${{ env.INSTALL_REF }}-x86_64.tar.gz
|
||||
|
||||
build_haiku_x86_64:
|
||||
name: Haiku x86_64 Nightly
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
with: { fetch-depth: 0, submodules: 'recursive' }
|
||||
- name: Set Environment Variables
|
||||
run: |
|
||||
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
||||
echo "RARCH=$(uname -m)" >> "$GITHUB_ENV"
|
||||
- uses: vmactions/haiku-vm@v1
|
||||
env:
|
||||
INSTALL_REF: ${{ needs.release.outputs.version }}
|
||||
with:
|
||||
envs: 'INSTALL_REF'
|
||||
usesh: true
|
||||
mem: 8192
|
||||
disable-cache: true
|
||||
prepare: |
|
||||
pkgman install -y libsdl2 libsdl2_devel git gcc premake5
|
||||
run: |
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
bash projects/scripts/patch_commit_number.sh
|
||||
sh projects/haiku/ecode/build.app.sh --version ${{ env.INSTALL_REF }}
|
||||
- name: Upload Files
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ needs.release.outputs.version }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
files: |
|
||||
projects/haiku/ecode/ecode-haiku-${{ env.INSTALL_REF }}-x86_64.tar.gz
|
||||
# build_haiku_x86_64:
|
||||
# name: Haiku x86_64 Nightly
|
||||
# needs: release
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout Code
|
||||
# uses: actions/checkout@v6
|
||||
# with: { fetch-depth: 0, submodules: 'recursive' }
|
||||
# - name: Set Environment Variables
|
||||
# run: |
|
||||
# echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
||||
# echo "RARCH=$(uname -m)" >> "$GITHUB_ENV"
|
||||
# - uses: vmactions/haiku-vm@v1
|
||||
# env:
|
||||
# INSTALL_REF: ${{ needs.release.outputs.version }}
|
||||
# with:
|
||||
# envs: 'INSTALL_REF'
|
||||
# usesh: true
|
||||
# mem: 8192
|
||||
# disable-cache: true
|
||||
# prepare: |
|
||||
# pkgman install -y libsdl2 libsdl2_devel git gcc premake5
|
||||
# run: |
|
||||
# git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
# bash projects/scripts/patch_commit_number.sh
|
||||
# sh projects/haiku/ecode/build.app.sh --version ${{ env.INSTALL_REF }}
|
||||
# - name: Upload Files
|
||||
# uses: softprops/action-gh-release@v3
|
||||
# with:
|
||||
# tag_name: ${{ needs.release.outputs.version }}
|
||||
# draft: false
|
||||
# prerelease: true
|
||||
# files: |
|
||||
# projects/haiku/ecode/ecode-haiku-${{ env.INSTALL_REF }}-x86_64.tar.gz
|
||||
|
||||
build_windows_msvc:
|
||||
name: Windows x86_64 MSVC Nightly
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
@@ -103,7 +103,6 @@ MarkdownView h2 {
|
||||
}
|
||||
|
||||
MarkdownView img {
|
||||
scale-type: fit-inside;
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ class EE_API BatchRenderer {
|
||||
/** Set the predefined blending function to use on the batch */
|
||||
void setBlendMode( const BlendMode& blend );
|
||||
|
||||
/** Selects RGB subpixel coverage compositing for subsequently queued textured quads. */
|
||||
void setSubpixelText( bool enabled );
|
||||
|
||||
/** Set if every batch call have to be immediately rendered */
|
||||
void setBatchForceRendering( const bool& force ) { mForceRendering = force; }
|
||||
|
||||
@@ -339,6 +342,7 @@ class EE_API BatchRenderer {
|
||||
|
||||
bool mForceRendering{ false };
|
||||
bool mForceBlendMode{ true };
|
||||
bool mSubpixelText{ false };
|
||||
|
||||
void flush();
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ struct EE_API Glyph {
|
||||
Sizef size; ///< The glyph bitmap size on screen
|
||||
int lsbDelta{ 0 }; //!< Left offset after forced autohint. Internally used by getKerning()
|
||||
int rsbDelta{ 0 }; //!< Right offset after forced autohint. Internally used by getKerning()
|
||||
Font* font{ nullptr }; ///< The glyph font
|
||||
GlyphRenderMode renderMode{ GlyphRenderMode::Mask }; ///< Atlas texel compositing mode
|
||||
Font* font{ nullptr }; ///< The glyph font
|
||||
};
|
||||
|
||||
enum class FontType { TTF, BMF, Sprite };
|
||||
@@ -47,7 +48,7 @@ enum class FontAntialiasing { None, Grayscale, Subpixel };
|
||||
/** @brief Font interface class. */
|
||||
class EE_API Font {
|
||||
public:
|
||||
enum Event { Load, Unload };
|
||||
enum Event { Load, Unload, CacheClear };
|
||||
|
||||
typedef std::function<void( Uint32, Event, Font* )> FontEventCallback;
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace EE { namespace Graphics {
|
||||
|
||||
class VertexBuffer;
|
||||
|
||||
enum class GlyphRenderMode : Uint8 { Mask, Subpixel, Color };
|
||||
|
||||
class EE_API GlyphDrawable : public DrawableResource {
|
||||
public:
|
||||
static GlyphDrawable* New( TexturePtr texture, const Rect& srcRect, const Sizef& destSize = {},
|
||||
@@ -70,6 +72,10 @@ class EE_API GlyphDrawable : public DrawableResource {
|
||||
|
||||
void setAdvance( Float advance );
|
||||
|
||||
GlyphRenderMode getGlyphRenderMode() const;
|
||||
|
||||
void setGlyphRenderMode( GlyphRenderMode renderMode );
|
||||
|
||||
protected:
|
||||
TexturePtr mTexture;
|
||||
Rectf mSrcRect;
|
||||
@@ -78,6 +84,7 @@ class EE_API GlyphDrawable : public DrawableResource {
|
||||
Vector2f mGlyphOffset;
|
||||
DrawMode mDrawMode{ DrawMode::Image };
|
||||
Float mAdvance{ 0 };
|
||||
GlyphRenderMode mGlyphRenderMode{ GlyphRenderMode::Mask };
|
||||
bool mIsItalic{ false };
|
||||
};
|
||||
|
||||
|
||||
@@ -148,7 +148,6 @@ typedef char GLchar;
|
||||
#ifndef GL_NUM_EXTENSIONS
|
||||
#define GL_NUM_EXTENSIONS 0x821D
|
||||
#endif
|
||||
|
||||
#else
|
||||
//! Mobile platform ( Android / iPhone / Maemo )
|
||||
#ifndef GL_GLEXT_PROTOTYPES
|
||||
@@ -398,6 +397,19 @@ typedef char GLchar;
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef GL_SRC1_COLOR
|
||||
#define GL_SRC1_COLOR 0x88F9
|
||||
#endif
|
||||
#ifndef GL_ONE_MINUS_SRC1_COLOR
|
||||
#define GL_ONE_MINUS_SRC1_COLOR 0x88FA
|
||||
#endif
|
||||
#ifndef GL_ONE
|
||||
#define GL_ONE 1
|
||||
#endif
|
||||
#ifndef GL_ONE_MINUS_SRC_ALPHA
|
||||
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
|
||||
#endif
|
||||
|
||||
#ifndef GL_MULTISAMPLE
|
||||
#define GL_MULTISAMPLE 0x809D
|
||||
#endif
|
||||
|
||||
@@ -90,6 +90,12 @@ class EE_API Renderer {
|
||||
|
||||
void drawArrays( unsigned int mode, int first, int count );
|
||||
|
||||
/** Draws an LCD coverage range into RGB independently and updates destination alpha. */
|
||||
bool drawSubpixelArrays( unsigned int mode, int first, int count );
|
||||
|
||||
/** Draws an LCD coverage range as neutral grayscale using its mean coverage. */
|
||||
bool drawSubpixelFallbackArrays( unsigned int mode, int first, int count );
|
||||
|
||||
void drawElements( unsigned int mode, int count, unsigned int type, const void* indices );
|
||||
|
||||
void bindTexture( unsigned int target, unsigned int texture );
|
||||
@@ -105,6 +111,9 @@ class EE_API Renderer {
|
||||
|
||||
void blendEquationSeparate( unsigned int modeRGB, unsigned int modeAlpha );
|
||||
|
||||
bool bindFragDataLocationIndexed( unsigned int program, unsigned int colorNumber,
|
||||
unsigned int index, const char* name );
|
||||
|
||||
void blitFrameBuffer( int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0,
|
||||
int dstX1, int dstY1, unsigned int mask, unsigned int filter );
|
||||
|
||||
@@ -197,6 +206,9 @@ class EE_API Renderer {
|
||||
|
||||
virtual void setShader( ShaderProgram* Shader );
|
||||
|
||||
/** Selects built-in texture sampling: normal RGBA (0), LCD R/G/B (1-3), or LCD mean (4). */
|
||||
virtual bool setTextureColorMode( Int32 mode );
|
||||
|
||||
virtual void clip2DPlaneEnable( const Int32& x, const Int32& y, const Int32& Width,
|
||||
const Int32& Height ) = 0;
|
||||
|
||||
@@ -237,6 +249,8 @@ class EE_API Renderer {
|
||||
|
||||
void colorMask( Uint8 red, Uint8 green, Uint8 blue, Uint8 alpha );
|
||||
|
||||
void getColorMask( Uint8 mask[4] ) const;
|
||||
|
||||
void bindVertexArray( unsigned int array );
|
||||
|
||||
void deleteVertexArrays( int n, const unsigned int* arrays );
|
||||
@@ -351,6 +365,10 @@ class EE_API Renderer {
|
||||
void waitForIdle();
|
||||
|
||||
protected:
|
||||
virtual bool drawSubpixelDualSourceArrays( unsigned int mode, int first, int count );
|
||||
|
||||
static const Vector3ff& textureColorChannel( Int32 mode );
|
||||
|
||||
static Renderer* sSingleton;
|
||||
|
||||
enum RendererStateFlags {
|
||||
@@ -366,6 +384,7 @@ class EE_API Renderer {
|
||||
int mQuadVertex;
|
||||
float mLineWidth;
|
||||
unsigned int mCurVAO;
|
||||
Uint8 mColorMask[4]{ 1, 1, 1, 1 };
|
||||
|
||||
ClippingMask* mClippingMask;
|
||||
|
||||
|
||||
@@ -83,7 +83,22 @@ class EE_API RendererGL : public Renderer {
|
||||
const float projMatrix[16], const int viewport[4], float* objx, float* objy,
|
||||
float* objz );
|
||||
|
||||
void setShader( ShaderProgram* shader );
|
||||
|
||||
bool setTextureColorMode( Int32 mode );
|
||||
|
||||
protected:
|
||||
bool ensureSubpixelShader();
|
||||
bool ensureSubpixelDualSourceShader();
|
||||
bool drawSubpixelDualSourceArrays( unsigned int mode, int first, int count );
|
||||
|
||||
ShaderProgramPtr mSubpixelShader;
|
||||
ShaderProgramPtr mSubpixelDualSourceShader;
|
||||
ShaderProgram* mPreviousShader{ nullptr };
|
||||
Int32 mSubpixelChannelLoc{ -1 };
|
||||
bool mUsingSubpixelShader{ false };
|
||||
bool mSubpixelShaderInitializationAttempted{ false };
|
||||
bool mSubpixelDualSourceShaderInitializationAttempted{ false };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -87,6 +87,8 @@ class EE_API RendererGL3 : public RendererGLShader {
|
||||
void planeStateCheck( bool tryEnable );
|
||||
|
||||
void reloadShader( ShaderProgram* Shader );
|
||||
ShaderProgramPtr createSubpixelDualSourceShader();
|
||||
bool canUseSubpixelDualSourceShader() const;
|
||||
};
|
||||
|
||||
}} // namespace EE::Graphics
|
||||
|
||||
@@ -96,6 +96,8 @@ class EE_API RendererGL3CP : public RendererGLShader {
|
||||
void reloadShader( ShaderProgram* Shader );
|
||||
|
||||
void allocateBuffers( const Uint32& size );
|
||||
ShaderProgramPtr createSubpixelDualSourceShader();
|
||||
bool canUseSubpixelDualSourceShader() const;
|
||||
};
|
||||
|
||||
}} // namespace EE::Graphics
|
||||
|
||||
@@ -98,6 +98,8 @@ class EE_API RendererGLES2 : public RendererGLShader {
|
||||
void reloadShader( ShaderProgram* Shader );
|
||||
|
||||
void checkLocalShader();
|
||||
ShaderProgramPtr createSubpixelDualSourceShader();
|
||||
bool canUseSubpixelDualSourceShader() const;
|
||||
};
|
||||
|
||||
}} // namespace EE::Graphics
|
||||
|
||||
@@ -54,14 +54,29 @@ class EE_API RendererGLShader : public Renderer {
|
||||
const float projMatrix[16], const int viewport[4], float* objx, float* objy,
|
||||
float* objz );
|
||||
|
||||
bool setTextureColorMode( Int32 mode );
|
||||
|
||||
protected:
|
||||
bool drawSubpixelDualSourceArrays( unsigned int mode, int first, int count );
|
||||
ShaderProgramPtr createSubpixelDualSourceShader( const std::string& vertexShader,
|
||||
const std::string& fragmentShader,
|
||||
bool bindFragmentOutputs );
|
||||
static const char* subpixelDualSourceFragmentShaderGLSL130();
|
||||
virtual ShaderProgramPtr createSubpixelDualSourceShader() = 0;
|
||||
virtual bool canUseSubpixelDualSourceShader() const = 0;
|
||||
|
||||
Private::MatrixStack* mStack;
|
||||
int mProjectionMatrix_id; // cpu-side hook to shader uniform
|
||||
int mModelViewMatrix_id; // cpu-side hook to shader uniform
|
||||
int mTextureMatrix_id; // cpu-side hook to shader uniform
|
||||
int mTextureColorMode_id;
|
||||
int mTextureColorChannel_id;
|
||||
Int32 mTextureColorMode;
|
||||
unsigned int mCurrentMode;
|
||||
ShaderProgram* mCurShader;
|
||||
ShaderProgram* mShaderPrev;
|
||||
ShaderProgramPtr mSubpixelDualSourceShader;
|
||||
bool mSubpixelDualSourceShaderInitializationAttempted{ false };
|
||||
|
||||
void updateMatrix();
|
||||
};
|
||||
|
||||
@@ -33,6 +33,8 @@ enum GraphicsLibraryExtension {
|
||||
EEGL_EXT_blend_minmax,
|
||||
EEGL_EXT_blend_subtract,
|
||||
EEGL_ARB_ES3_compatibility,
|
||||
EEGL_ARB_blend_func_extended,
|
||||
EEGL_EXT_blend_func_extended,
|
||||
};
|
||||
|
||||
/// Graphics Library Renderer version available.
|
||||
|
||||
@@ -56,6 +56,9 @@ class EE_API ShaderProgram {
|
||||
/** Add a vector of shaders */
|
||||
void addShaders( const std::vector<ShaderPtr>& shaders );
|
||||
|
||||
/** Bind a fragment output to a color number and source index before linking. */
|
||||
bool bindFragDataLocationIndexed( Uint32 colorNumber, Uint32 index, const char* name );
|
||||
|
||||
virtual bool link();
|
||||
|
||||
/** @return If the shader program is valid */
|
||||
@@ -160,6 +163,12 @@ class EE_API ShaderProgram {
|
||||
std::vector<ShaderPtr> mShaders;
|
||||
std::map<std::string, Int32> mUniformLocations;
|
||||
std::map<std::string, Int32> mAttributeLocations;
|
||||
struct FragmentOutputBinding {
|
||||
Uint32 colorNumber;
|
||||
Uint32 index;
|
||||
std::string name;
|
||||
};
|
||||
std::vector<FragmentOutputBinding> mFragmentOutputBindings;
|
||||
|
||||
ShaderProgramReloadCb mReloadCb;
|
||||
|
||||
|
||||
@@ -410,7 +410,7 @@ class EE_API Text {
|
||||
Float mCachedWidth{ 0 };
|
||||
Uint32 mAlign{ TEXT_ALIGN_LEFT };
|
||||
Uint32 mTabWidth{ 4 };
|
||||
Uint32 mInvalidationId{ 0 };
|
||||
Uint32 mInvalidationId{ GlobalInvalidationId };
|
||||
Uint32 mTextHints{ 0 };
|
||||
Float mMaxWrapWidth{ 0 };
|
||||
LineWrapMode mLineWrapMode{ LineWrapMode::NoWrap };
|
||||
@@ -422,8 +422,10 @@ class EE_API Text {
|
||||
|
||||
std::vector<VertexCoords> mVertices;
|
||||
std::vector<Color> mColors;
|
||||
std::vector<GlyphRenderMode> mRenderModes;
|
||||
std::vector<VertexCoords> mOutlineVertices;
|
||||
std::vector<Color> mOutlineColors;
|
||||
std::vector<GlyphRenderMode> mOutlineRenderModes;
|
||||
|
||||
void ensureGeometryUpdate();
|
||||
|
||||
@@ -432,12 +434,12 @@ class EE_API Text {
|
||||
/** Force to cache the width of the current text */
|
||||
void cacheWidth();
|
||||
|
||||
static void addLine( std::vector<VertexCoords>& vertices, Float lineLength, Float lineTop,
|
||||
Float offset, Float thickness, Float outlineThickness, Int32 centerDiffX );
|
||||
void addLine( std::vector<VertexCoords>& vertices, Float lineLength, Float lineTop,
|
||||
Float offset, Float thickness, Float outlineThickness, Int32 centerDiffX );
|
||||
|
||||
static void addGlyphQuad( std::vector<VertexCoords>& vertices, Vector2f position,
|
||||
const EE::Graphics::Glyph& glyph, Float italic,
|
||||
Float outlineThickness, Int32 centerDiffX );
|
||||
void addGlyphQuad( std::vector<VertexCoords>& vertices, Vector2f position,
|
||||
const EE::Graphics::Glyph& glyph, Float italic, Float outlineThickness,
|
||||
Int32 centerDiffX );
|
||||
|
||||
Uint32 getTotalVertices();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef EE_UI_UIAPPLICATION
|
||||
#define EE_UI_UIAPPLICATION
|
||||
|
||||
#include <eepp/graphics/font.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
|
||||
#include <optional>
|
||||
@@ -45,6 +46,10 @@ class EE_API UIApplication {
|
||||
//! The default fallback font for the UI. If not provided it will load Droid Sans Fallback
|
||||
//! Full ( it will look at "assets/fonts/DroidSansFallbackFull.ttf" )
|
||||
Font* fallbackFont{ nullptr };
|
||||
//! The hinting policy applied to fonts owned by the default and UI resource scopes.
|
||||
FontHinting fontHinting{ FontHinting::Full };
|
||||
//! The antialiasing policy applied to fonts owned by the default and UI resource scopes.
|
||||
FontAntialiasing fontAntialiasing{ FontAntialiasing::Grayscale };
|
||||
};
|
||||
|
||||
UIApplication( const WindowSettings& windowSettings, const Settings& appSettings = Settings(),
|
||||
@@ -70,6 +75,7 @@ class EE_API UIApplication {
|
||||
bool showMemoryManagerResult() const;
|
||||
|
||||
String::HashType getStyleSheetDefaultMarker() const { return mStyleSheetMarker; }
|
||||
|
||||
protected:
|
||||
UISceneNode* mUISceneNode{ nullptr };
|
||||
EE::Window::Window* mWindow{ nullptr };
|
||||
|
||||
@@ -161,9 +161,9 @@ class EE_API UISceneNode : public SceneNode {
|
||||
* @brief Binds an embedded scene to host-scene services without copying document state.
|
||||
*
|
||||
* Copies only shared platform/configuration services: dispatcher, DPI/window pointer,
|
||||
* thread pool, color/contrast preferences, and default font/theme pointers. Stylesheets,
|
||||
* URI, referer, cookies, navigation callbacks, actions, roots, resource scope, and dirty queues
|
||||
* remain owned by this scene.
|
||||
* thread pool, color/contrast and font-rendering preferences, and default font/theme pointers.
|
||||
* Stylesheets, URI, referer, cookies, navigation callbacks, actions, roots, resource scope, and
|
||||
* dirty queues remain owned by this scene.
|
||||
*/
|
||||
void initializeEmbeddedFromHost( UISceneNode* hostScene );
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ void BatchRenderer::setTexture( const Texture* texture, Texture::CoordinateType
|
||||
if ( mTexture != texture || mCoordinateType != coordinateType ) {
|
||||
flush();
|
||||
mTextureOwner.reset();
|
||||
mSubpixelText = false;
|
||||
}
|
||||
|
||||
mTexture = texture;
|
||||
@@ -81,6 +82,12 @@ void BatchRenderer::setBlendMode( const BlendMode& blend ) {
|
||||
mBlend = blend;
|
||||
}
|
||||
|
||||
void BatchRenderer::setSubpixelText( bool enabled ) {
|
||||
if ( enabled != mSubpixelText )
|
||||
flush();
|
||||
mSubpixelText = enabled;
|
||||
}
|
||||
|
||||
void BatchRenderer::addVertices( const unsigned int& num ) {
|
||||
mNumVertex += num;
|
||||
|
||||
@@ -151,17 +158,21 @@ void BatchRenderer::flush() {
|
||||
4, GL_UNSIGNED_BYTE, sizeof( VertexData ),
|
||||
reinterpret_cast<char*>( &mVertex[0] ) + sizeof( Vector2f ) + sizeof( Vector2f ), alloc );
|
||||
|
||||
if ( !GLi->quadsSupported() ) {
|
||||
if ( PRIMITIVE_QUADS == mCurrentMode ) {
|
||||
GLi->drawArrays( PRIMITIVE_TRIANGLES, 0, NumVertex );
|
||||
} else if ( PRIMITIVE_POLYGON == mCurrentMode ) {
|
||||
GLi->drawArrays( PRIMITIVE_TRIANGLE_FAN, 0, NumVertex );
|
||||
} else {
|
||||
GLi->drawArrays( mCurrentMode, 0, NumVertex );
|
||||
}
|
||||
} else {
|
||||
GLi->drawArrays( mCurrentMode, 0, NumVertex );
|
||||
unsigned int drawMode = mCurrentMode;
|
||||
if ( !GLi->quadsSupported() && PRIMITIVE_QUADS == mCurrentMode )
|
||||
drawMode = PRIMITIVE_TRIANGLES;
|
||||
else if ( !GLi->quadsSupported() && PRIMITIVE_POLYGON == mCurrentMode )
|
||||
drawMode = PRIMITIVE_TRIANGLE_FAN;
|
||||
|
||||
bool drawn = false;
|
||||
if ( mSubpixelText && NULL != mTexture ) {
|
||||
if ( mRotation == 0.f && mScale == 1.f )
|
||||
drawn = GLi->drawSubpixelArrays( drawMode, 0, NumVertex );
|
||||
if ( !drawn )
|
||||
drawn = GLi->drawSubpixelFallbackArrays( drawMode, 0, NumVertex );
|
||||
}
|
||||
if ( !drawn )
|
||||
GLi->drawArrays( drawMode, 0, NumVertex );
|
||||
|
||||
if ( createMatrix ) {
|
||||
GLi->popMatrix();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <eepp/core/retainsymbol.hpp>
|
||||
#include <eepp/graphics/fontservice.hpp>
|
||||
#include <eepp/graphics/fonttruetype.hpp>
|
||||
#include <eepp/graphics/renderer/renderer.hpp>
|
||||
#include <eepp/graphics/resourcescope.hpp>
|
||||
#include <eepp/graphics/systemfontresolver.hpp>
|
||||
#include <eepp/graphics/text.hpp>
|
||||
@@ -931,6 +932,7 @@ GlyphDrawable* FontTrueType::getGlyphDrawable( Uint32 codePoint, unsigned int ch
|
||||
getGlyphTopOffset( characterSize ) + glyph.bounds.Top - outlineThickness } );
|
||||
region->setAdvance( glyph.advance );
|
||||
region->setIsItalic( isItalic );
|
||||
region->setGlyphRenderMode( glyph.renderMode );
|
||||
|
||||
drawables[key] = region;
|
||||
return region;
|
||||
@@ -960,6 +962,7 @@ GlyphDrawable* FontTrueType::getGlyphDrawableFromGlyphIndex( Uint32 glyphIndex,
|
||||
getGlyphTopOffset( characterSize ) + glyph.bounds.Top - outlineThickness } );
|
||||
region->setAdvance( glyph.advance );
|
||||
region->setIsItalic( italic );
|
||||
region->setGlyphRenderMode( glyph.renderMode );
|
||||
|
||||
drawables[key] = region;
|
||||
return region;
|
||||
@@ -1453,6 +1456,14 @@ Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize,
|
||||
}
|
||||
|
||||
FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>( glyphDesc )->bitmap;
|
||||
if ( bitmap.pixel_mode == FT_PIXEL_MODE_LCD ) {
|
||||
const bool programmableRenderer =
|
||||
GLi &&
|
||||
( ( GLi->version() == GLv_2 && GLi->shadersSupported() ) || GLi->version() == GLv_3 ||
|
||||
GLi->version() == GLv_3CP || GLi->version() == GLv_ES2 );
|
||||
glyph.renderMode = programmableRenderer ? GlyphRenderMode::Subpixel : GlyphRenderMode::Mask;
|
||||
} else if ( bitmap.pixel_mode == FT_PIXEL_MODE_BGRA )
|
||||
glyph.renderMode = GlyphRenderMode::Color;
|
||||
|
||||
// Apply bold if necessary -- fallback technique using bitmap (lower quality)
|
||||
if ( !outline ) {
|
||||
@@ -1549,6 +1560,14 @@ Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize,
|
||||
glyph.bounds.Bottom =
|
||||
static_cast<Float>( slot->metrics.height ) / static_cast<Float>( 1 << 6 ) +
|
||||
outlineThickness * 2;
|
||||
if ( bitmap.pixel_mode == FT_PIXEL_MODE_LCD ) {
|
||||
const FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>( glyphDesc );
|
||||
// LCD filtering may shift the bitmap beyond the outline bearing. Keep the logical
|
||||
// dimensions expected by retained Text, but position the filtered ink from its actual
|
||||
// bitmap origin.
|
||||
glyph.bounds.Left = bitmapGlyph->left + outlineThickness;
|
||||
glyph.bounds.Top = -bitmapGlyph->top + outlineThickness;
|
||||
}
|
||||
|
||||
// Resize the pixel buffer to the new size and fill it with transparent white pixels
|
||||
const Uint32 bufferSize = width * height * 4;
|
||||
@@ -1602,17 +1621,50 @@ Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize,
|
||||
destHeight = dest.getHeight() + 2 * padding;
|
||||
}
|
||||
} else if ( bitmap.pixel_mode == FT_PIXEL_MODE_LCD ) {
|
||||
for ( int y = padding; y < height - padding; ++y ) {
|
||||
for ( int x = padding; x < width - padding; ++x ) {
|
||||
const std::size_t index = ( x + y * width ) * 4;
|
||||
const Uint8* px = &pixels[( x - padding ) * 3];
|
||||
mPixelBuffer[index + 0] = px[0];
|
||||
mPixelBuffer[index + 1] = px[1];
|
||||
mPixelBuffer[index + 2] = px[2];
|
||||
mPixelBuffer[index + 3] =
|
||||
(Uint8)( ( (int)px[0] + (int)px[1] + (int)px[2] ) / 3.f );
|
||||
if ( scale < 1.f ) {
|
||||
for ( int y = 0; y < height; ++y ) {
|
||||
for ( int x = 0; x < width; ++x ) {
|
||||
const std::size_t index = ( x + y * width ) * 4;
|
||||
const Uint8* px = &pixels[x * 3];
|
||||
mPixelBuffer[index + 0] =
|
||||
glyph.renderMode == GlyphRenderMode::Subpixel ? px[0] : 255;
|
||||
mPixelBuffer[index + 1] =
|
||||
glyph.renderMode == GlyphRenderMode::Subpixel ? px[1] : 255;
|
||||
mPixelBuffer[index + 2] =
|
||||
glyph.renderMode == GlyphRenderMode::Subpixel ? px[2] : 255;
|
||||
mPixelBuffer[index + 3] =
|
||||
(Uint8)( ( (int)px[0] + (int)px[1] + (int)px[2] ) / 3.f );
|
||||
}
|
||||
pixels += bitmap.pitch;
|
||||
}
|
||||
|
||||
Image dest( &mPixelBuffer[0], width, height, 4 );
|
||||
dest.avoidFreeImage( true );
|
||||
dest.scale( scale );
|
||||
dest.avoidFreeImage( true );
|
||||
pixelPtr = dest.getPixels();
|
||||
glyph.bounds.Left *= scale;
|
||||
glyph.bounds.Right *= scale;
|
||||
glyph.bounds.Top *= scale;
|
||||
glyph.bounds.Bottom *= scale;
|
||||
destWidth = dest.getWidth() + 2 * padding;
|
||||
destHeight = dest.getHeight() + 2 * padding;
|
||||
} else {
|
||||
for ( int y = padding; y < height - padding; ++y ) {
|
||||
for ( int x = padding; x < width - padding; ++x ) {
|
||||
const std::size_t index = ( x + y * width ) * 4;
|
||||
const Uint8* px = &pixels[( x - padding ) * 3];
|
||||
mPixelBuffer[index + 0] =
|
||||
glyph.renderMode == GlyphRenderMode::Subpixel ? px[0] : 255;
|
||||
mPixelBuffer[index + 1] =
|
||||
glyph.renderMode == GlyphRenderMode::Subpixel ? px[1] : 255;
|
||||
mPixelBuffer[index + 2] =
|
||||
glyph.renderMode == GlyphRenderMode::Subpixel ? px[2] : 255;
|
||||
mPixelBuffer[index + 3] =
|
||||
(Uint8)( ( (int)px[0] + (int)px[1] + (int)px[2] ) / 3.f );
|
||||
}
|
||||
pixels += bitmap.pitch;
|
||||
}
|
||||
pixels += bitmap.pitch;
|
||||
}
|
||||
} else {
|
||||
if ( scale < 1.f ) {
|
||||
@@ -2042,6 +2094,7 @@ FontTrueType::Page::~Page() {
|
||||
}
|
||||
|
||||
void FontTrueType::clearCache() {
|
||||
sendEvent( Event::CacheClear );
|
||||
mPages.clear();
|
||||
mClosestCharacterSize.clear();
|
||||
mCodePointIndexCache.clear();
|
||||
|
||||
@@ -36,6 +36,7 @@ void GlyphDrawable::draw( const Vector2f& position, const Sizef& size ) {
|
||||
BatchRenderer* BR = GlobalBatchRenderer::instance();
|
||||
BR->setTexture( mTexture, mTexture->getCoordinateType() );
|
||||
BR->setBlendMode( BlendMode::Alpha() );
|
||||
BR->setSubpixelText( mGlyphRenderMode == GlyphRenderMode::Subpixel );
|
||||
BR->quadsBegin();
|
||||
BR->quadsSetColor( mColor );
|
||||
BR->quadsSetTexCoord( mSrcRect.Left, mSrcRect.Top, mSrcRect.Left + mSrcRect.Right,
|
||||
@@ -87,6 +88,7 @@ DrawablePtr GlyphDrawable::clone() const {
|
||||
instance->setDrawMode( mDrawMode );
|
||||
instance->setIsItalic( mIsItalic );
|
||||
instance->setAdvance( mAdvance );
|
||||
instance->setGlyphRenderMode( mGlyphRenderMode );
|
||||
instance->setColor( mColor );
|
||||
instance->setPosition( mPosition );
|
||||
return instance;
|
||||
@@ -152,4 +154,12 @@ void GlyphDrawable::setAdvance( Float advance ) {
|
||||
mAdvance = advance;
|
||||
}
|
||||
|
||||
GlyphRenderMode GlyphDrawable::getGlyphRenderMode() const {
|
||||
return mGlyphRenderMode;
|
||||
}
|
||||
|
||||
void GlyphDrawable::setGlyphRenderMode( GlyphRenderMode renderMode ) {
|
||||
mGlyphRenderMode = renderMode;
|
||||
}
|
||||
|
||||
}} // namespace EE::Graphics
|
||||
|
||||
@@ -44,6 +44,8 @@ typedef void( APIENTRY* pglBlendFuncSeparate )( GLenum sfactorRGB, GLenum dfacto
|
||||
typedef void( APIENTRY* pglDiscardFramebufferEXT )( GLenum target, GLsizei numAttachments,
|
||||
const GLenum* attachments );
|
||||
typedef void( APIENTRY* pglBlendEquationSeparate )( GLenum modeRGB, GLenum modeAlpha );
|
||||
typedef void( APIENTRY* pglBindFragDataLocationIndexed )( GLuint program, GLuint colorNumber,
|
||||
GLuint index, const GLchar* name );
|
||||
typedef void( APIENTRY* pglBlitFramebufferEXT )( GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
||||
GLbitfield mask, GLenum filter );
|
||||
|
||||
@@ -260,6 +260,9 @@ void Renderer::init() {
|
||||
writeExtension( EEGL_EXT_blend_func_separate, GLEW_EXT_blend_func_separate );
|
||||
writeExtension( EEGL_EXT_blend_minmax, GLEW_EXT_blend_minmax );
|
||||
writeExtension( EEGL_EXT_blend_subtract, GLEW_EXT_blend_subtract );
|
||||
writeExtension( EEGL_ARB_blend_func_extended,
|
||||
GLEW_ARB_blend_func_extended || GLEW_VERSION_3_3 );
|
||||
writeExtension( EEGL_EXT_blend_func_extended, GLEW_EXT_blend_func_extended );
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
@@ -296,6 +299,13 @@ void Renderer::init() {
|
||||
glVersion >= 140 || isExtension( "GL_EXT_blend_minmax" ) );
|
||||
writeExtension( EEGL_EXT_blend_subtract,
|
||||
glVersion >= 140 || isExtension( "GL_EXT_blend_subtract" ) );
|
||||
writeExtension( EEGL_ARB_blend_func_extended,
|
||||
!is_es &&
|
||||
( glVersion >= 330 || isExtension( "GL_ARB_blend_func_extended" ) ) );
|
||||
writeExtension( EEGL_EXT_blend_func_extended,
|
||||
is_es && ( isExtension( "GL_EXT_blend_func_extended" ) ||
|
||||
isExtension( "GL_WEBGL_blend_func_extended" ) ||
|
||||
isExtension( "WEBGL_blend_func_extended" ) ) );
|
||||
}
|
||||
|
||||
// NVIDIA added support for GL_OES_compressed_ETC1_RGB8_texture in desktop GPUs
|
||||
@@ -476,6 +486,42 @@ void Renderer::drawArrays( unsigned int mode, int first, int count ) {
|
||||
glDrawArrays( mode, first, count );
|
||||
}
|
||||
|
||||
bool Renderer::drawSubpixelArrays( unsigned int mode, int first, int count ) {
|
||||
if ( drawSubpixelDualSourceArrays( mode, first, count ) )
|
||||
return true;
|
||||
|
||||
if ( !setTextureColorMode( 1 ) )
|
||||
return false;
|
||||
|
||||
Uint8 previousColorMask[4];
|
||||
getColorMask( previousColorMask );
|
||||
for ( Int32 channel = 0; channel < 3; ++channel ) {
|
||||
setTextureColorMode( channel + 1 );
|
||||
colorMask( channel == 0 && previousColorMask[0], channel == 1 && previousColorMask[1],
|
||||
channel == 2 && previousColorMask[2], 0 );
|
||||
drawArrays( mode, first, count );
|
||||
}
|
||||
setTextureColorMode( 4 );
|
||||
colorMask( 0, 0, 0, previousColorMask[3] );
|
||||
drawArrays( mode, first, count );
|
||||
setTextureColorMode( 0 );
|
||||
colorMask( previousColorMask[0], previousColorMask[1], previousColorMask[2],
|
||||
previousColorMask[3] );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Renderer::drawSubpixelDualSourceArrays( unsigned int, int, int ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Renderer::drawSubpixelFallbackArrays( unsigned int mode, int first, int count ) {
|
||||
if ( !setTextureColorMode( 4 ) )
|
||||
return false;
|
||||
drawArrays( mode, first, count );
|
||||
setTextureColorMode( 0 );
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::drawElements( unsigned int mode, int count, unsigned int type,
|
||||
const void* indices ) {
|
||||
glDrawElements( mode, count, type, indices );
|
||||
@@ -534,6 +580,29 @@ void Renderer::blendEquationSeparate( unsigned int modeRGB, unsigned int modeAlp
|
||||
eeglBlendEquationSeparate( modeRGB, modeAlpha );
|
||||
}
|
||||
|
||||
bool Renderer::bindFragDataLocationIndexed( unsigned int program, unsigned int colorNumber,
|
||||
unsigned int index, const char* name ) {
|
||||
#ifndef EE_GLES
|
||||
static pglBindFragDataLocationIndexed bindFragDataLocationIndexed = NULL;
|
||||
if ( NULL == bindFragDataLocationIndexed )
|
||||
bindFragDataLocationIndexed =
|
||||
(pglBindFragDataLocationIndexed)getProcAddress( "glBindFragDataLocationIndexed" );
|
||||
if ( NULL != bindFragDataLocationIndexed ) {
|
||||
bindFragDataLocationIndexed( program, colorNumber, index, name );
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
static pglBindFragDataLocationIndexed bindFragDataLocationIndexedEXT = NULL;
|
||||
if ( NULL == bindFragDataLocationIndexedEXT )
|
||||
bindFragDataLocationIndexedEXT =
|
||||
(pglBindFragDataLocationIndexed)getProcAddress( "glBindFragDataLocationIndexedEXT" );
|
||||
if ( NULL != bindFragDataLocationIndexedEXT ) {
|
||||
bindFragDataLocationIndexedEXT( program, colorNumber, index, name );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Renderer::blitFrameBuffer( int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0,
|
||||
int dstX1, int dstY1, unsigned int mask, unsigned int filter ) {
|
||||
static pglBlitFramebufferEXT eeglBlitFramebufferEXT = NULL;
|
||||
@@ -556,6 +625,19 @@ void Renderer::setShader( ShaderProgram* Shader ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Renderer::setTextureColorMode( Int32 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Vector3ff& Renderer::textureColorChannel( Int32 mode ) {
|
||||
static const Vector3ff channels[] = { { 0.f, 0.f, 0.f },
|
||||
{ 1.f, 0.f, 0.f },
|
||||
{ 0.f, 1.f, 0.f },
|
||||
{ 0.f, 0.f, 1.f },
|
||||
{ 1.f / 3.f, 1.f / 3.f, 1.f / 3.f } };
|
||||
return channels[mode];
|
||||
}
|
||||
|
||||
bool Renderer::isLineSmooth() {
|
||||
return BitOp::readBitKey( &mStateFlags, RSF_LINE_SMOOTH );
|
||||
}
|
||||
@@ -743,9 +825,17 @@ void Renderer::stencilMask( unsigned int mask ) {
|
||||
}
|
||||
|
||||
void Renderer::colorMask( Uint8 red, Uint8 green, Uint8 blue, Uint8 alpha ) {
|
||||
mColorMask[0] = red;
|
||||
mColorMask[1] = green;
|
||||
mColorMask[2] = blue;
|
||||
mColorMask[3] = alpha;
|
||||
glColorMask( red, green, blue, alpha );
|
||||
}
|
||||
|
||||
void Renderer::getColorMask( Uint8 mask[4] ) const {
|
||||
std::copy( std::begin( mColorMask ), std::end( mColorMask ), mask );
|
||||
}
|
||||
|
||||
const int& Renderer::quadVertex() const {
|
||||
return mQuadVertex;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <eepp/graphics/blendmode.hpp>
|
||||
#include <eepp/graphics/renderer/openglext.hpp>
|
||||
#include <eepp/graphics/renderer/renderergl.hpp>
|
||||
|
||||
@@ -61,6 +62,151 @@ RendererGL::RendererGL() {
|
||||
|
||||
RendererGL::~RendererGL() {}
|
||||
|
||||
bool RendererGL::ensureSubpixelShader() {
|
||||
#ifndef EE_GLES1
|
||||
if ( mSubpixelShader )
|
||||
return true;
|
||||
if ( mSubpixelShaderInitializationAttempted || !shadersSupported() )
|
||||
return false;
|
||||
mSubpixelShaderInitializationAttempted = true;
|
||||
static const char vertexShader[] = R"(
|
||||
#version 120
|
||||
varying vec4 dgl_Color;
|
||||
varying vec2 dgl_TexCoord;
|
||||
void main() {
|
||||
gl_Position = ftransform();
|
||||
gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;
|
||||
dgl_Color = gl_Color;
|
||||
dgl_TexCoord = ( gl_TextureMatrix[0] * gl_MultiTexCoord0 ).xy;
|
||||
}
|
||||
)";
|
||||
static const char fragmentShader[] = R"(
|
||||
#version 120
|
||||
uniform sampler2D textureUnit0;
|
||||
uniform vec3 dgl_TextureColorChannel;
|
||||
varying vec4 dgl_Color;
|
||||
varying vec2 dgl_TexCoord;
|
||||
void main() {
|
||||
vec4 texel = texture2D( textureUnit0, dgl_TexCoord );
|
||||
float coverage = dot( texel.rgb, dgl_TextureColorChannel );
|
||||
gl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * coverage );
|
||||
}
|
||||
)";
|
||||
mSubpixelShader = ShaderProgram::New( vertexShader, sizeof( vertexShader ) - 1, fragmentShader,
|
||||
sizeof( fragmentShader ) - 1, "eepp-subpixel-text" );
|
||||
if ( mSubpixelShader && mSubpixelShader->isValid() ) {
|
||||
Renderer::setShader( mSubpixelShader.get() );
|
||||
mSubpixelChannelLoc = mSubpixelShader->getUniformLocation( "dgl_TextureColorChannel" );
|
||||
mSubpixelShader->setUniform( "textureUnit0", 0 );
|
||||
Renderer::setShader( mPreviousShader );
|
||||
} else {
|
||||
mSubpixelShader.reset();
|
||||
}
|
||||
return mSubpixelShader && mSubpixelChannelLoc != -1;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RendererGL::ensureSubpixelDualSourceShader() {
|
||||
#ifndef EE_GLES1
|
||||
if ( mSubpixelDualSourceShader )
|
||||
return true;
|
||||
if ( mSubpixelDualSourceShaderInitializationAttempted ||
|
||||
!isExtension( EEGL_ARB_blend_func_extended ) || !shadersSupported() )
|
||||
return false;
|
||||
mSubpixelDualSourceShaderInitializationAttempted = true;
|
||||
static const char vertexShader[] = R"(
|
||||
#version 130
|
||||
varying vec4 dgl_Color;
|
||||
varying vec2 dgl_TexCoord;
|
||||
void main() {
|
||||
gl_Position = ftransform();
|
||||
gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;
|
||||
dgl_Color = gl_Color;
|
||||
dgl_TexCoord = ( gl_TextureMatrix[0] * gl_MultiTexCoord0 ).xy;
|
||||
}
|
||||
)";
|
||||
static const char fragmentShader[] = R"(
|
||||
#version 130
|
||||
uniform sampler2D textureUnit0;
|
||||
varying vec4 dgl_Color;
|
||||
varying vec2 dgl_TexCoord;
|
||||
out vec4 dgl_FragColor;
|
||||
out vec4 dgl_FragCoverage;
|
||||
void main() {
|
||||
vec3 coverage = texture2D( textureUnit0, dgl_TexCoord ).rgb;
|
||||
float meanCoverage = dot( coverage, vec3( 1.0 / 3.0 ) );
|
||||
dgl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * meanCoverage );
|
||||
dgl_FragCoverage = vec4( dgl_Color.a * coverage, 0.0 );
|
||||
}
|
||||
)";
|
||||
ShaderProgramPtr shader = ShaderProgram::New( "eepp-subpixel-dual-source-text" );
|
||||
ShaderPtr vs( eeNew( VertexShader, ( vertexShader, sizeof( vertexShader ) - 1 ) ),
|
||||
ResourceDeleter<Shader>() );
|
||||
ShaderPtr fs( eeNew( FragmentShader, ( fragmentShader, sizeof( fragmentShader ) - 1 ) ),
|
||||
ResourceDeleter<Shader>() );
|
||||
if ( shader && vs->isValid() && fs->isValid() ) {
|
||||
shader->addShader( std::move( vs ) );
|
||||
shader->addShader( std::move( fs ) );
|
||||
if ( shader->bindFragDataLocationIndexed( 0, 0, "dgl_FragColor" ) &&
|
||||
shader->bindFragDataLocationIndexed( 0, 1, "dgl_FragCoverage" ) && shader->link() ) {
|
||||
mSubpixelDualSourceShader = std::move( shader );
|
||||
Renderer::setShader( mSubpixelDualSourceShader.get() );
|
||||
mSubpixelDualSourceShader->setUniform( "textureUnit0", 0 );
|
||||
Renderer::setShader( mPreviousShader );
|
||||
}
|
||||
}
|
||||
return !!mSubpixelDualSourceShader;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RendererGL::drawSubpixelDualSourceArrays( unsigned int mode, int first, int count ) {
|
||||
if ( !ensureSubpixelDualSourceShader() )
|
||||
return false;
|
||||
const BlendMode previousBlendMode = BlendMode::getPreBlendFunc();
|
||||
Renderer::setShader( mSubpixelDualSourceShader.get() );
|
||||
blendFuncSeparate( GL_SRC1_COLOR, GL_ONE_MINUS_SRC1_COLOR, GL_ONE, GL_ONE_MINUS_SRC_ALPHA );
|
||||
drawArrays( mode, first, count );
|
||||
BlendMode::setMode( previousBlendMode, true );
|
||||
Renderer::setShader( mPreviousShader );
|
||||
return true;
|
||||
}
|
||||
|
||||
void RendererGL::setShader( ShaderProgram* shader ) {
|
||||
if ( !mUsingSubpixelShader )
|
||||
mPreviousShader = shader;
|
||||
Renderer::setShader( shader );
|
||||
}
|
||||
|
||||
bool RendererGL::setTextureColorMode( Int32 mode ) {
|
||||
#ifdef EE_GLES1
|
||||
return false;
|
||||
#else
|
||||
if ( mode < 0 || mode > 4 )
|
||||
return false;
|
||||
if ( mode != 0 && !ensureSubpixelShader() )
|
||||
return false;
|
||||
if ( !mSubpixelShader || !mSubpixelShader->isValid() || mSubpixelChannelLoc == -1 )
|
||||
return false;
|
||||
if ( mode == 0 ) {
|
||||
if ( mUsingSubpixelShader ) {
|
||||
Renderer::setShader( mPreviousShader );
|
||||
mUsingSubpixelShader = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ( !mUsingSubpixelShader ) {
|
||||
Renderer::setShader( mSubpixelShader.get() );
|
||||
mUsingSubpixelShader = true;
|
||||
}
|
||||
mSubpixelShader->setUniform( mSubpixelChannelLoc, textureColorChannel( mode ) );
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
GraphicsLibraryVersion RendererGL::version() {
|
||||
#ifndef EE_GLES1
|
||||
return GLv_2;
|
||||
|
||||
@@ -115,6 +115,21 @@ void RendererGL3::reloadCurrentShader() {
|
||||
reloadShader( mCurShader );
|
||||
}
|
||||
|
||||
ShaderProgramPtr RendererGL3::createSubpixelDualSourceShader() {
|
||||
std::string vertexShader = mBaseVertexShader;
|
||||
String::replaceAll( vertexShader, "#version 120", "#version 130" );
|
||||
return RendererGLShader::createSubpixelDualSourceShader(
|
||||
vertexShader, subpixelDualSourceFragmentShaderGLSL130(), true );
|
||||
}
|
||||
|
||||
bool RendererGL3::canUseSubpixelDualSourceShader() const {
|
||||
for ( Int32 state : mPlanesStates ) {
|
||||
if ( state != 0 )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RendererGL3::reloadShader( ShaderProgram* Shader ) {
|
||||
mCurShader = NULL;
|
||||
|
||||
@@ -148,6 +163,8 @@ void RendererGL3::setShader( ShaderProgram* Shader ) {
|
||||
mProjectionMatrix_id = mCurShader->getUniformLocation( "dgl_ProjectionMatrix" );
|
||||
mModelViewMatrix_id = mCurShader->getUniformLocation( "dgl_ModelViewMatrix" );
|
||||
mTextureMatrix_id = mCurShader->getUniformLocation( "dgl_TextureMatrix" );
|
||||
mTextureColorMode_id = mCurShader->getUniformLocation( "dgl_TextureColorMode" );
|
||||
mTextureColorChannel_id = mCurShader->getUniformLocation( "dgl_TextureColorChannel" );
|
||||
mTexActiveLoc = mCurShader->getUniformLocation( "dgl_TexActive" );
|
||||
mPointSpriteLoc = mCurShader->getUniformLocation( "dgl_PointSpriteActive" );
|
||||
mClippingEnabledLoc = mCurShader->getUniformLocation( "dgl_ClippingEnabled" );
|
||||
@@ -168,6 +185,11 @@ void RendererGL3::setShader( ShaderProgram* Shader ) {
|
||||
}
|
||||
|
||||
useProgram( mCurShader->getHandler() );
|
||||
if ( mTextureColorMode_id != -1 )
|
||||
mCurShader->setUniform( mTextureColorMode_id, mTextureColorMode );
|
||||
if ( mTextureColorChannel_id != -1 && mTextureColorMode != 0 ) {
|
||||
mCurShader->setUniform( mTextureColorChannel_id, textureColorChannel( mTextureColorMode ) );
|
||||
}
|
||||
|
||||
if ( -1 != mAttribsLoc[EEGL_VERTEX_ARRAY] )
|
||||
enableClientState( GL_VERTEX_ARRAY );
|
||||
|
||||
@@ -147,6 +147,32 @@ void RendererGL3CP::reloadCurrentShader() {
|
||||
reloadShader( mCurShader );
|
||||
}
|
||||
|
||||
ShaderProgramPtr RendererGL3CP::createSubpixelDualSourceShader() {
|
||||
static const char fragmentShader[] = R"(#version 330
|
||||
uniform sampler2D textureUnit0;
|
||||
in vec4 dgl_Color;
|
||||
in vec4 dgl_TexCoord[1];
|
||||
out vec4 dgl_FragColor;
|
||||
out vec4 dgl_FragCoverage;
|
||||
void main() {
|
||||
vec3 coverage = texture( textureUnit0, dgl_TexCoord[0].xy ).rgb;
|
||||
float meanCoverage = dot( coverage, vec3( 1.0 / 3.0 ) );
|
||||
dgl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * meanCoverage );
|
||||
dgl_FragCoverage = vec4( dgl_Color.a * coverage, 0.0 );
|
||||
}
|
||||
)";
|
||||
return RendererGLShader::createSubpixelDualSourceShader( mBaseVertexShader, fragmentShader,
|
||||
true );
|
||||
}
|
||||
|
||||
bool RendererGL3CP::canUseSubpixelDualSourceShader() const {
|
||||
for ( Int32 state : mPlanesStates ) {
|
||||
if ( state != 0 )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RendererGL3CP::reloadShader( ShaderProgram* Shader ) {
|
||||
mCurShader = NULL;
|
||||
|
||||
@@ -175,6 +201,8 @@ void RendererGL3CP::setShader( ShaderProgram* Shader ) {
|
||||
mProjectionMatrix_id = mCurShader->getUniformLocation( "dgl_ProjectionMatrix" );
|
||||
mModelViewMatrix_id = mCurShader->getUniformLocation( "dgl_ModelViewMatrix" );
|
||||
mTextureMatrix_id = mCurShader->getUniformLocation( "dgl_TextureMatrix" );
|
||||
mTextureColorMode_id = mCurShader->getUniformLocation( "dgl_TextureColorMode" );
|
||||
mTextureColorChannel_id = mCurShader->getUniformLocation( "dgl_TextureColorChannel" );
|
||||
mTexActiveLoc = mCurShader->getUniformLocation( "dgl_TexActive" );
|
||||
mPointSpriteLoc = mCurShader->getUniformLocation( "dgl_PointSpriteActive" );
|
||||
mClippingEnabledLoc = mCurShader->getUniformLocation( "dgl_ClippingEnabled" );
|
||||
@@ -195,6 +223,11 @@ void RendererGL3CP::setShader( ShaderProgram* Shader ) {
|
||||
}
|
||||
|
||||
useProgram( mCurShader->getHandler() );
|
||||
if ( mTextureColorMode_id != -1 )
|
||||
mCurShader->setUniform( mTextureColorMode_id, mTextureColorMode );
|
||||
if ( mTextureColorChannel_id != -1 && mTextureColorMode != 0 ) {
|
||||
mCurShader->setUniform( mTextureColorChannel_id, textureColorChannel( mTextureColorMode ) );
|
||||
}
|
||||
|
||||
if ( -1 != mAttribsLoc[EEGL_VERTEX_ARRAY] )
|
||||
enableClientState( GL_VERTEX_ARRAY );
|
||||
|
||||
@@ -148,6 +148,35 @@ void RendererGLES2::reloadCurrentShader() {
|
||||
reloadShader( mCurShader );
|
||||
}
|
||||
|
||||
ShaderProgramPtr RendererGLES2::createSubpixelDualSourceShader() {
|
||||
#ifdef EE_GLES2
|
||||
static const char fragmentShader[] = R"(#extension GL_EXT_blend_func_extended : require
|
||||
precision mediump float;
|
||||
precision lowp int;
|
||||
uniform sampler2D textureUnit0;
|
||||
varying vec4 dgl_Color;
|
||||
varying mediump vec4 dgl_TexCoord[1];
|
||||
void main() {
|
||||
vec3 coverage = texture2D( textureUnit0, dgl_TexCoord[0].xy ).rgb;
|
||||
float meanCoverage = dot( coverage, vec3( 1.0 / 3.0 ) );
|
||||
gl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * meanCoverage );
|
||||
gl_SecondaryFragColorEXT = vec4( dgl_Color.a * coverage, 0.0 );
|
||||
}
|
||||
)";
|
||||
return RendererGLShader::createSubpixelDualSourceShader( mBaseVertexShader, fragmentShader,
|
||||
false );
|
||||
#else
|
||||
std::string vertexShader = mBaseVertexShader;
|
||||
String::replaceAll( vertexShader, "#version 120", "#version 130" );
|
||||
return RendererGLShader::createSubpixelDualSourceShader(
|
||||
vertexShader, subpixelDualSourceFragmentShaderGLSL130(), true );
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RendererGLES2::canUseSubpixelDualSourceShader() const {
|
||||
return !mClippingEnabled && !mPointSpriteEnabled;
|
||||
}
|
||||
|
||||
void RendererGLES2::reloadShader( ShaderProgram* Shader ) {
|
||||
mCurShader = NULL;
|
||||
|
||||
@@ -198,6 +227,8 @@ void RendererGLES2::setShader( ShaderProgram* Shader ) {
|
||||
mProjectionMatrix_id = mCurShader->getUniformLocation( "dgl_ProjectionMatrix" );
|
||||
mModelViewMatrix_id = mCurShader->getUniformLocation( "dgl_ModelViewMatrix" );
|
||||
mTextureMatrix_id = mCurShader->getUniformLocation( "dgl_TextureMatrix" );
|
||||
mTextureColorMode_id = mCurShader->getUniformLocation( "dgl_TextureColorMode" );
|
||||
mTextureColorChannel_id = mCurShader->getUniformLocation( "dgl_TextureColorChannel" );
|
||||
mTexActiveLoc = mCurShader->getUniformLocation( "dgl_TexActive" );
|
||||
mClippingEnabledLoc = mCurShader->getUniformLocation( "dgl_ClippingEnabled" );
|
||||
mPointSizeLoc = mCurShader->getUniformLocation( "dgl_PointSize" );
|
||||
@@ -222,6 +253,11 @@ void RendererGLES2::setShader( ShaderProgram* Shader ) {
|
||||
}
|
||||
|
||||
useProgram( mCurShader->getHandler() );
|
||||
if ( mTextureColorMode_id != -1 )
|
||||
mCurShader->setUniform( mTextureColorMode_id, mTextureColorMode );
|
||||
if ( mTextureColorChannel_id != -1 && mTextureColorMode != 0 ) {
|
||||
mCurShader->setUniform( mTextureColorChannel_id, textureColorChannel( mTextureColorMode ) );
|
||||
}
|
||||
|
||||
if ( -1 != mAttribsLoc[EEGL_VERTEX_ARRAY] )
|
||||
enableClientState( GL_VERTEX_ARRAY );
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <eepp/graphics/blendmode.hpp>
|
||||
#include <eepp/graphics/renderer/openglext.hpp>
|
||||
#include <eepp/graphics/renderer/rendererglshader.hpp>
|
||||
#include <eepp/graphics/renderer/rendererstackhelper.hpp>
|
||||
@@ -8,6 +9,9 @@ RendererGLShader::RendererGLShader() :
|
||||
mProjectionMatrix_id( 0 ),
|
||||
mModelViewMatrix_id( 0 ),
|
||||
mTextureMatrix_id( 0 ),
|
||||
mTextureColorMode_id( -1 ),
|
||||
mTextureColorChannel_id( -1 ),
|
||||
mTextureColorMode( 0 ),
|
||||
mCurrentMode( 0 ),
|
||||
mCurShader( NULL ),
|
||||
mShaderPrev( NULL ) {
|
||||
@@ -21,6 +25,78 @@ RendererGLShader::~RendererGLShader() {
|
||||
eeSAFE_DELETE( mStack );
|
||||
}
|
||||
|
||||
const char* RendererGLShader::subpixelDualSourceFragmentShaderGLSL130() {
|
||||
return R"(#version 130
|
||||
uniform sampler2D textureUnit0;
|
||||
varying vec4 dgl_Color;
|
||||
varying vec4 dgl_TexCoord[1];
|
||||
out vec4 dgl_FragColor;
|
||||
out vec4 dgl_FragCoverage;
|
||||
void main() {
|
||||
vec3 coverage = texture2D( textureUnit0, dgl_TexCoord[0].xy ).rgb;
|
||||
float meanCoverage = dot( coverage, vec3( 1.0 / 3.0 ) );
|
||||
dgl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * meanCoverage );
|
||||
dgl_FragCoverage = vec4( dgl_Color.a * coverage, 0.0 );
|
||||
}
|
||||
)";
|
||||
}
|
||||
|
||||
ShaderProgramPtr RendererGLShader::createSubpixelDualSourceShader(
|
||||
const std::string& vertexShader, const std::string& fragmentShader, bool bindFragmentOutputs ) {
|
||||
ShaderProgramPtr shader = ShaderProgram::New( "eepp-subpixel-dual-source-text" );
|
||||
const bool shaderEnsure = Shader::ensure();
|
||||
Shader::ensure( false );
|
||||
ShaderPtr vs( eeNew( VertexShader, ( vertexShader.c_str(), vertexShader.size() ) ),
|
||||
ResourceDeleter<Shader>() );
|
||||
ShaderPtr fs( eeNew( FragmentShader, ( fragmentShader.c_str(), fragmentShader.size() ) ),
|
||||
ResourceDeleter<Shader>() );
|
||||
Shader::ensure( shaderEnsure );
|
||||
if ( !shader || !vs->isValid() || !fs->isValid() )
|
||||
return {};
|
||||
shader->addShader( std::move( vs ) );
|
||||
shader->addShader( std::move( fs ) );
|
||||
if ( bindFragmentOutputs &&
|
||||
( !shader->bindFragDataLocationIndexed( 0, 0, "dgl_FragColor" ) ||
|
||||
!shader->bindFragDataLocationIndexed( 0, 1, "dgl_FragCoverage" ) ) )
|
||||
return {};
|
||||
return shader->link() ? shader : ShaderProgramPtr{};
|
||||
}
|
||||
|
||||
bool RendererGLShader::drawSubpixelDualSourceArrays( unsigned int mode, int first, int count ) {
|
||||
if ( !isExtension( EEGL_ARB_blend_func_extended ) &&
|
||||
!isExtension( EEGL_EXT_blend_func_extended ) )
|
||||
return false;
|
||||
if ( !canUseSubpixelDualSourceShader() )
|
||||
return false;
|
||||
if ( !mSubpixelDualSourceShader && !mSubpixelDualSourceShaderInitializationAttempted ) {
|
||||
mSubpixelDualSourceShaderInitializationAttempted = true;
|
||||
mSubpixelDualSourceShader = createSubpixelDualSourceShader();
|
||||
}
|
||||
if ( !mSubpixelDualSourceShader )
|
||||
return false;
|
||||
ShaderProgram* previousShader = mCurShader;
|
||||
const BlendMode previousBlendMode = BlendMode::getPreBlendFunc();
|
||||
setShader( mSubpixelDualSourceShader.get() );
|
||||
blendFuncSeparate( GL_SRC1_COLOR, GL_ONE_MINUS_SRC1_COLOR, GL_ONE, GL_ONE_MINUS_SRC_ALPHA );
|
||||
drawArrays( mode, first, count );
|
||||
BlendMode::setMode( previousBlendMode, true );
|
||||
setShader( previousShader );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RendererGLShader::setTextureColorMode( Int32 mode ) {
|
||||
if ( mTextureColorMode_id == -1 )
|
||||
return false;
|
||||
if ( mTextureColorMode != mode ) {
|
||||
mTextureColorMode = mode;
|
||||
mCurShader->setUniform( mTextureColorMode_id, mode );
|
||||
if ( mTextureColorChannel_id != -1 ) {
|
||||
mCurShader->setUniform( mTextureColorChannel_id, textureColorChannel( mode ) );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RendererGLShader::updateMatrix() {
|
||||
switch ( mCurrentMode ) {
|
||||
case GL_PROJECTION: {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
const GLchar * EEGLES2_SHADER_BASE_FS = R"(
|
||||
uniform sampler2D textureUnit0;
|
||||
uniform int dgl_TextureColorMode;
|
||||
uniform vec3 dgl_TextureColorChannel;
|
||||
varying vec4 dgl_Color;
|
||||
#ifndef GL_ES
|
||||
varying vec4 dgl_TexCoord[ 1 ];
|
||||
@@ -9,6 +11,12 @@ varying mediump vec4 dgl_TexCoord[ 1 ];
|
||||
#endif
|
||||
void main(void)
|
||||
{
|
||||
gl_FragColor = dgl_Color * texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
vec4 texel = texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 0 == dgl_TextureColorMode )
|
||||
gl_FragColor = dgl_Color * texel;
|
||||
else {
|
||||
float coverage = dot( texel.rgb, dgl_TextureColorChannel );
|
||||
gl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * coverage );
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
@@ -3,6 +3,8 @@ const GLchar * EEGL3_SHADER_BASE_FS = R"(
|
||||
#define MAX_CLIP_PLANES 6
|
||||
uniform sampler2D textureUnit0;
|
||||
uniform int dgl_TexActive;
|
||||
uniform int dgl_TextureColorMode;
|
||||
uniform vec3 dgl_TextureColorChannel;
|
||||
uniform int dgl_PointSpriteActive;
|
||||
uniform int dgl_ClippingEnabled;
|
||||
uniform int dgl_ClipEnabled[ MAX_CLIP_PLANES ];
|
||||
@@ -20,8 +22,15 @@ void main(void)
|
||||
}
|
||||
}
|
||||
if ( 0 == dgl_PointSpriteActive ) {
|
||||
if ( 1 == dgl_TexActive )
|
||||
gl_FragColor = dgl_Color * texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 1 == dgl_TexActive ) {
|
||||
vec4 texel = texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 0 == dgl_TextureColorMode )
|
||||
gl_FragColor = dgl_Color * texel;
|
||||
else {
|
||||
float coverage = dot( texel.rgb, dgl_TextureColorChannel );
|
||||
gl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * coverage );
|
||||
}
|
||||
}
|
||||
else
|
||||
gl_FragColor = dgl_Color;
|
||||
} else
|
||||
|
||||
@@ -3,6 +3,8 @@ const GLchar * EEGL3CP_SHADER_BASE_FS = R"(
|
||||
#define MAX_CLIP_PLANES 6
|
||||
uniform sampler2D textureUnit0;
|
||||
uniform int dgl_TexActive;
|
||||
uniform int dgl_TextureColorMode;
|
||||
uniform vec3 dgl_TextureColorChannel;
|
||||
uniform int dgl_PointSpriteActive;
|
||||
uniform int dgl_ClippingEnabled;
|
||||
uniform int dgl_ClipEnabled[ MAX_CLIP_PLANES ];
|
||||
@@ -21,8 +23,15 @@ void main(void)
|
||||
}
|
||||
}
|
||||
if ( 0 == dgl_PointSpriteActive ) {
|
||||
if ( 1 == dgl_TexActive )
|
||||
dgl_FragColor = dgl_Color * texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 1 == dgl_TexActive ) {
|
||||
vec4 texel = texture( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 0 == dgl_TextureColorMode )
|
||||
dgl_FragColor = dgl_Color * texel;
|
||||
else {
|
||||
float coverage = dot( texel.rgb, dgl_TextureColorChannel );
|
||||
dgl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * coverage );
|
||||
}
|
||||
}
|
||||
else
|
||||
dgl_FragColor = dgl_Color;
|
||||
} else
|
||||
|
||||
@@ -3,6 +3,8 @@ const GLchar * EEGL3CP_SHADER_BASE_FS = R"(
|
||||
#define MAX_CLIP_PLANES 6
|
||||
uniform sampler2D textureUnit0;
|
||||
uniform int dgl_TexActive;
|
||||
uniform int dgl_TextureColorMode;
|
||||
uniform vec3 dgl_TextureColorChannel;
|
||||
uniform int dgl_PointSpriteActive;
|
||||
uniform int dgl_ClippingEnabled;
|
||||
uniform int dgl_ClipEnabled[ MAX_CLIP_PLANES ];
|
||||
@@ -20,8 +22,15 @@ void main(void)
|
||||
}
|
||||
}
|
||||
if ( 0 == dgl_PointSpriteActive ) {
|
||||
if ( 1 == dgl_TexActive )
|
||||
gl_FragColor = dgl_Color * texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 1 == dgl_TexActive ) {
|
||||
vec4 texel = texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 0 == dgl_TextureColorMode )
|
||||
gl_FragColor = dgl_Color * texel;
|
||||
else {
|
||||
float coverage = dot( texel.rgb, dgl_TextureColorChannel );
|
||||
gl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * coverage );
|
||||
}
|
||||
}
|
||||
else
|
||||
gl_FragColor = dgl_Color;
|
||||
} else
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
const GLchar * EEGLES2_SHADER_CLIPPED_FS = R"(#define MAX_CLIP_PLANES 6
|
||||
uniform sampler2D textureUnit0;
|
||||
uniform int dgl_TexActive;
|
||||
uniform int dgl_TextureColorMode;
|
||||
uniform vec3 dgl_TextureColorChannel;
|
||||
#ifndef GL_ES
|
||||
uniform int dgl_ClippingEnabled;
|
||||
uniform int dgl_ClipEnabled[ MAX_CLIP_PLANES ];
|
||||
@@ -26,10 +28,16 @@ void main(void)
|
||||
discard;
|
||||
}
|
||||
}
|
||||
if ( 1 == dgl_TexActive )
|
||||
gl_FragColor = dgl_Color * texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 1 == dgl_TexActive ) {
|
||||
vec4 texel = texture2D( textureUnit0, dgl_TexCoord[ 0 ].xy );
|
||||
if ( 0 == dgl_TextureColorMode )
|
||||
gl_FragColor = dgl_Color * texel;
|
||||
else {
|
||||
float coverage = dot( texel.rgb, dgl_TextureColorChannel );
|
||||
gl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * coverage );
|
||||
}
|
||||
}
|
||||
else
|
||||
gl_FragColor = dgl_Color;
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
@@ -231,8 +231,19 @@ void ShaderProgram::addShaders( const std::vector<ShaderPtr>& shaders ) {
|
||||
addShader( shader );
|
||||
}
|
||||
|
||||
bool ShaderProgram::bindFragDataLocationIndexed( Uint32 colorNumber, Uint32 index,
|
||||
const char* name ) {
|
||||
if ( !GLi->bindFragDataLocationIndexed( getHandler(), colorNumber, index, name ) )
|
||||
return false;
|
||||
mFragmentOutputBindings.push_back( { colorNumber, index, name } );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderProgram::link() {
|
||||
#ifdef EE_SHADERS_SUPPORTED
|
||||
for ( const auto& binding : mFragmentOutputBindings )
|
||||
GLi->bindFragDataLocationIndexed( getHandler(), binding.colorNumber, binding.index,
|
||||
binding.name.c_str() );
|
||||
GLi->linkProgram( getHandler() );
|
||||
|
||||
Int32 linked;
|
||||
|
||||
+64
-17
@@ -231,6 +231,7 @@ Text* Text::New( Font* font, unsigned int characterSize ) {
|
||||
|
||||
static inline void drawGlyph( BatchRenderer* BR, GlyphDrawable* gd, const Vector2f& position,
|
||||
const Color& color, bool isItalic ) {
|
||||
BR->setSubpixelText( gd->getGlyphRenderMode() == GlyphRenderMode::Subpixel );
|
||||
BR->quadsSetColor( color );
|
||||
BR->quadsSetTexCoord( gd->getSrcRect().Left, gd->getSrcRect().Top,
|
||||
gd->getSrcRect().Left + gd->getSrcRect().Right,
|
||||
@@ -253,6 +254,7 @@ static inline void _drawUnderline( Font* font, Float fontSize, const Color& font
|
||||
Float outlineThickness, const Vector2f& pos, Float width,
|
||||
const Color& shadowColor, const Vector2f& shadowOffset,
|
||||
const Color& outlineColor ) {
|
||||
BR->setSubpixelText( false );
|
||||
Float underlineOffset = font->getUnderlinePosition( fontSize );
|
||||
Float underlineThickness = font->getUnderlineThickness( fontSize );
|
||||
Float top =
|
||||
@@ -294,6 +296,7 @@ static inline void _drawStrikeThrough( Font* font, Float fontSize, const Color&
|
||||
Float outlineThickness, const Vector2f& pos, Float width,
|
||||
const Color& shadowColor, const Vector2f& shadowOffset,
|
||||
const Color& outlineColor ) {
|
||||
BR->setSubpixelText( false );
|
||||
Rectf xBounds =
|
||||
font->getGlyph( L'x', fontSize, style & Text::Bold, style & Text::Italic ).bounds;
|
||||
Float strikeThroughOffset = xBounds.Top + xBounds.Bottom * 0.5f;
|
||||
@@ -1683,6 +1686,36 @@ Float Text::getLineSpacing() const {
|
||||
: 0;
|
||||
}
|
||||
|
||||
static void drawTextVertexRanges( const std::vector<GlyphRenderMode>& renderModes,
|
||||
unsigned int numVertices, bool allowSubpixel ) {
|
||||
const unsigned int verticesPerQuad = GLi->quadVertex();
|
||||
const unsigned int primitive = GLi->quadsSupported() ? GL_QUADS : GL_TRIANGLES;
|
||||
if ( renderModes.empty() || renderModes.size() * verticesPerQuad != numVertices ) {
|
||||
GLi->drawArrays( primitive, 0, numVertices );
|
||||
return;
|
||||
}
|
||||
|
||||
size_t rangeStart = 0;
|
||||
while ( rangeStart < renderModes.size() ) {
|
||||
size_t rangeEnd = rangeStart + 1;
|
||||
while ( rangeEnd < renderModes.size() && renderModes[rangeEnd] == renderModes[rangeStart] )
|
||||
++rangeEnd;
|
||||
|
||||
const int first = rangeStart * verticesPerQuad;
|
||||
const int count = ( rangeEnd - rangeStart ) * verticesPerQuad;
|
||||
bool drawn = false;
|
||||
if ( renderModes[rangeStart] == GlyphRenderMode::Subpixel ) {
|
||||
if ( allowSubpixel )
|
||||
drawn = GLi->drawSubpixelArrays( primitive, first, count );
|
||||
if ( !drawn )
|
||||
drawn = GLi->drawSubpixelFallbackArrays( primitive, first, count );
|
||||
}
|
||||
if ( !drawn )
|
||||
GLi->drawArrays( primitive, first, count );
|
||||
rangeStart = rangeEnd;
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 Text::getTextHints() const {
|
||||
return mTextHints;
|
||||
}
|
||||
@@ -1699,6 +1732,9 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
|
||||
return;
|
||||
|
||||
unsigned int numvert = mVertices.size();
|
||||
const bool containsSubpixel = !mRenderModes.empty();
|
||||
const Float drawX = containsSubpixel && rotation == 0.f && scale == 1.f ? std::trunc( X ) : X;
|
||||
const Float drawY = containsSubpixel && rotation == 0.f && scale == 1.f ? std::trunc( Y ) : Y;
|
||||
|
||||
GlobalBatchRenderer::instance()->draw();
|
||||
|
||||
@@ -1729,7 +1765,7 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
|
||||
GLi->rotatef( rotation, 0.0f, 0.0f, 1.0f );
|
||||
GLi->translatef( -center.x + cX, -center.y + cY, 0.f );
|
||||
} else {
|
||||
GLi->translatef( X, Y, 0 );
|
||||
GLi->translatef( drawX, drawY, 0 );
|
||||
}
|
||||
|
||||
if ( backgroundColor != Color::Transparent ) {
|
||||
@@ -1759,7 +1795,7 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
|
||||
if ( rotation != 0.0f || scale != 1.0f ) {
|
||||
GLi->popMatrix();
|
||||
} else {
|
||||
GLi->translatef( -X, -Y, 0 );
|
||||
GLi->translatef( -drawX, -drawY, 0 );
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1772,43 +1808,36 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl
|
||||
return;
|
||||
texture->bind();
|
||||
BlendMode::setMode( effect );
|
||||
const bool allowSubpixel = effect == BlendMode::Alpha() && rotation == 0.f && scale == 1.f;
|
||||
|
||||
Uint32 alloc = numvert * sizeof( VertexCoords );
|
||||
Uint32 allocC = numvert * GLi->quadVertex();
|
||||
|
||||
if ( 0 != mFontStyleConfig.OutlineThickness ) {
|
||||
GLi->colorPointer( 4, GL_UNSIGNED_BYTE, 0,
|
||||
reinterpret_cast<const char*>( outlineColors.data() ), allocC );
|
||||
reinterpret_cast<const char*>( outlineColors.data() ),
|
||||
outlineColors.size() * sizeof( Color ) );
|
||||
GLi->texCoordPointer( 2, GL_FP, sizeof( VertexCoords ),
|
||||
reinterpret_cast<char*>( &mOutlineVertices[0] ), alloc );
|
||||
GLi->vertexPointer( 2, GL_FP, sizeof( VertexCoords ),
|
||||
reinterpret_cast<char*>( &mOutlineVertices[0] ) + sizeof( Float ) * 2,
|
||||
alloc );
|
||||
|
||||
if ( GLi->quadsSupported() ) {
|
||||
GLi->drawArrays( GL_QUADS, 0, numvert );
|
||||
} else {
|
||||
GLi->drawArrays( GL_TRIANGLES, 0, numvert );
|
||||
}
|
||||
drawTextVertexRanges( mOutlineRenderModes, numvert, allowSubpixel );
|
||||
}
|
||||
|
||||
GLi->colorPointer( 4, GL_UNSIGNED_BYTE, 0, reinterpret_cast<const char*>( colors.data() ),
|
||||
allocC );
|
||||
colors.size() * sizeof( Color ) );
|
||||
GLi->texCoordPointer( 2, GL_FP, sizeof( VertexCoords ),
|
||||
reinterpret_cast<char*>( &mVertices[0] ), alloc );
|
||||
GLi->vertexPointer( 2, GL_FP, sizeof( VertexCoords ),
|
||||
reinterpret_cast<char*>( &mVertices[0] ) + sizeof( Float ) * 2, alloc );
|
||||
|
||||
if ( GLi->quadsSupported() ) {
|
||||
GLi->drawArrays( GL_QUADS, 0, numvert );
|
||||
} else {
|
||||
GLi->drawArrays( GL_TRIANGLES, 0, numvert );
|
||||
}
|
||||
drawTextVertexRanges( mRenderModes, numvert, allowSubpixel );
|
||||
|
||||
if ( rotation != 0.0f || scale != 1.0f ) {
|
||||
GLi->popMatrix();
|
||||
} else {
|
||||
GLi->translatef( -X, -Y, 0 );
|
||||
GLi->translatef( -drawX, -drawY, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1860,7 +1889,9 @@ void Text::ensureGeometryUpdate() {
|
||||
|
||||
// Clear the previous geometry
|
||||
mVertices.clear();
|
||||
mRenderModes.clear();
|
||||
mOutlineVertices.clear();
|
||||
mOutlineRenderModes.clear();
|
||||
|
||||
if ( mCachedWidthNeedUpdate )
|
||||
mLinesWidth.clear();
|
||||
@@ -2650,6 +2681,9 @@ void Text::setFillColor( const std::vector<Color>& colors ) {
|
||||
// Add an underline or strikethrough line to the vertex array
|
||||
void Text::addLine( std::vector<VertexCoords>& vertices, Float lineLength, Float lineTop,
|
||||
Float offset, Float thickness, Float outlineThickness, Int32 centerDiffX ) {
|
||||
auto& renderModes = &vertices == &mOutlineVertices ? mOutlineRenderModes : mRenderModes;
|
||||
if ( !renderModes.empty() )
|
||||
renderModes.push_back( GlyphRenderMode::Mask );
|
||||
Float top = std::floor( lineTop + offset - ( thickness / 2 ) + 0.5f );
|
||||
Float bottom = top + std::floor( thickness + 0.5f );
|
||||
Float u1 = 0;
|
||||
@@ -2725,10 +2759,23 @@ void Text::addLine( std::vector<VertexCoords>& vertices, Float lineLength, Float
|
||||
void Text::addGlyphQuad( std::vector<VertexCoords>& vertices, Vector2f position,
|
||||
const EE::Graphics::Glyph& glyph, Float italic, Float outlineThickness,
|
||||
Int32 centerDiffX ) {
|
||||
auto& renderModes = &vertices == &mOutlineVertices ? mOutlineRenderModes : mRenderModes;
|
||||
if ( glyph.renderMode == GlyphRenderMode::Subpixel ) {
|
||||
if ( renderModes.empty() )
|
||||
renderModes.resize( vertices.size() / GLi->quadVertex(), GlyphRenderMode::Mask );
|
||||
renderModes.push_back( GlyphRenderMode::Subpixel );
|
||||
} else if ( !renderModes.empty() ) {
|
||||
renderModes.push_back( GlyphRenderMode::Mask );
|
||||
}
|
||||
if ( glyph.renderMode == GlyphRenderMode::Subpixel )
|
||||
position = position.trunc();
|
||||
Float padding = 1.0;
|
||||
Float left = glyph.bounds.Left - padding;
|
||||
Float top = glyph.bounds.Top - padding;
|
||||
Float right = glyph.bounds.Left + glyph.bounds.Right + padding;
|
||||
Float right = glyph.bounds.Left +
|
||||
( glyph.renderMode == GlyphRenderMode::Subpixel ? glyph.size.getWidth()
|
||||
: glyph.bounds.Right ) +
|
||||
padding;
|
||||
Float bottom = glyph.bounds.Top + glyph.bounds.Bottom + padding;
|
||||
|
||||
Float u1 = static_cast<Float>( glyph.textureRect.Left - padding );
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <eepp/graphics/fontfamily.hpp>
|
||||
#include <eepp/graphics/fontservice.hpp>
|
||||
#include <eepp/graphics/fonttruetype.hpp>
|
||||
#include <eepp/scene/scenemanager.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
@@ -54,7 +55,14 @@ UIApplication::UIApplication( const WindowSettings& windowSettings, const Settin
|
||||
FileSystem::changeWorkingDirectory( path );
|
||||
}
|
||||
|
||||
FontService& defaultFontService = defaultResourceScope().getFontService();
|
||||
defaultFontService.setHinting( appSettings.fontHinting );
|
||||
defaultFontService.setAntialiasing( appSettings.fontAntialiasing );
|
||||
|
||||
mUISceneNode = UISceneNode::New();
|
||||
FontService& uiFontService = mUISceneNode->getResourceScope()->getFontService();
|
||||
uiFontService.setHinting( appSettings.fontHinting );
|
||||
uiFontService.setAntialiasing( appSettings.fontAntialiasing );
|
||||
SceneManager::instance()->add( mUISceneNode );
|
||||
|
||||
if ( !appSettings.loadBaseResources )
|
||||
|
||||
@@ -65,7 +65,9 @@ UIGlyphIcon::UIGlyphIcon( const std::string& name, FontTrueType* font, const Uin
|
||||
UIIcon( name ), mFont( font ), mCodePoint( codePoint ) {
|
||||
eeASSERT( mFont );
|
||||
mCloseCb = mFont->pushFontEventCallback( [this]( Uint32, Font::Event event, Font* ) {
|
||||
if ( event == Font::Event::Unload )
|
||||
if ( event == Font::Event::CacheClear )
|
||||
mSizes.clear();
|
||||
else if ( event == Font::Event::Unload )
|
||||
mFont = nullptr;
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -317,6 +317,10 @@ void UISceneNode::initializeEmbeddedFromHost( UISceneNode* hostScene ) {
|
||||
mThreadPool = hostScene->getThreadPool();
|
||||
mColorSchemePreference = hostScene->getColorSchemePreference();
|
||||
mContrastPreference = hostScene->getContrastPreference();
|
||||
const FontService& hostFontService = hostScene->getResourceScope()->getFontService();
|
||||
FontService& fontService = mResourceScope->getFontService();
|
||||
fontService.setHinting( hostFontService.getHinting() );
|
||||
fontService.setAntialiasing( hostFontService.getAntialiasing() );
|
||||
|
||||
UIThemeManager* hostThemeManager = hostScene->getUIThemeManager();
|
||||
if ( hostThemeManager ) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <args/args.hxx>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
EE_MAIN_FUNC int main( int argc, char** argv ) {
|
||||
std::shared_ptr<ThreadPool> threadPool(
|
||||
@@ -25,6 +26,23 @@ EE_MAIN_FUNC int main( int argc, char** argv ) {
|
||||
args::ValueFlag<Float> pixelDensityConf( parser, "pixel-density",
|
||||
"Set default application pixel density",
|
||||
{ 'd', "pixel-density" } );
|
||||
const std::unordered_map<std::string, FontHinting> fontHintingMap{
|
||||
{ "none", FontHinting::None },
|
||||
{ "slight", FontHinting::Slight },
|
||||
{ "full", FontHinting::Full },
|
||||
};
|
||||
args::MapFlag<std::string, FontHinting> fontHinting(
|
||||
parser, "font-hinting", "Font hinting mode (accepted values: none, slight, full)",
|
||||
{ "font-hinting" }, fontHintingMap, FontHinting::Full );
|
||||
const std::unordered_map<std::string, FontAntialiasing> fontAntialiasingMap{
|
||||
{ "none", FontAntialiasing::None },
|
||||
{ "grayscale", FontAntialiasing::Grayscale },
|
||||
{ "subpixel", FontAntialiasing::Subpixel },
|
||||
};
|
||||
args::MapFlag<std::string, FontAntialiasing> fontAntialiasing(
|
||||
parser, "font-antialiasing",
|
||||
"Font antialiasing mode (accepted values: none, grayscale, subpixel)",
|
||||
{ "font-antialiasing" }, fontAntialiasingMap, FontAntialiasing::Grayscale );
|
||||
|
||||
try {
|
||||
parser.ParseCLI( Sys::parseArguments( argc, argv ) );
|
||||
@@ -41,10 +59,14 @@ EE_MAIN_FUNC int main( int argc, char** argv ) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
UIApplication::Settings appSettings( {}, pixelDensityConf ? pixelDensityConf.Get() : 0.f );
|
||||
appSettings.fontHinting = fontHinting.Get();
|
||||
appSettings.fontAntialiasing = fontAntialiasing.Get();
|
||||
|
||||
UIApplication app(
|
||||
WindowSettings{ 1280, 720, "eepp - UI HTML Example", WindowStyle::Default,
|
||||
WindowBackend::Default, 32, Sys::getProcessPath() + "assets/icon/ee.png" },
|
||||
UIApplication::Settings( {}, pixelDensityConf ? pixelDensityConf.Get() : 0.f ),
|
||||
appSettings,
|
||||
ContextSettings( false,
|
||||
benchmarkMode.Get() ? 0 : ContextSettings::FrameRateLimitScreenRefreshRate,
|
||||
4 ) );
|
||||
|
||||
@@ -209,6 +209,16 @@ class TerminalDisplay : public ITerminalDisplay {
|
||||
|
||||
void setFont( Font* font );
|
||||
|
||||
FontHinting getFontHinting() const;
|
||||
|
||||
/** Updates the externally-owned font hinting policy and invalidates cached terminal glyphs. */
|
||||
void setFontHinting( FontHinting fontHinting );
|
||||
|
||||
FontAntialiasing getFontAntialiasing() const;
|
||||
|
||||
/** Updates the externally-owned antialiasing policy and selects the matching draw path. */
|
||||
void setFontAntialiasing( FontAntialiasing fontAntialiasing );
|
||||
|
||||
const Float& getFontSize() const;
|
||||
|
||||
void setFontSize( const Float& FontSize );
|
||||
@@ -317,6 +327,8 @@ class TerminalDisplay : public ITerminalDisplay {
|
||||
Uint32 mColumns{ 0 };
|
||||
Uint32 mRows{ 0 };
|
||||
Uint32 mClickStep{ 5 };
|
||||
FontHinting mFontHinting{ FontHinting::Full };
|
||||
FontAntialiasing mFontAntialiasing{ FontAntialiasing::Grayscale };
|
||||
FrameBufferUniquePtr mFrameBuffer;
|
||||
VertexBufferUniquePtr mVBBackground;
|
||||
VertexBufferUniquePtr mVBForeground;
|
||||
|
||||
@@ -52,6 +52,9 @@ class UITerminal : public UIWidget {
|
||||
|
||||
void setFont( Font* font );
|
||||
|
||||
/** Synchronizes terminal rendering policy from the service that owns its font. */
|
||||
void syncFontRenderingConfig();
|
||||
|
||||
void setKeyBindings( const KeyBindings& keyBindings );
|
||||
|
||||
void addKeyBindingString( const std::string& shortcut, const std::string& command );
|
||||
|
||||
@@ -1214,6 +1214,9 @@ void TerminalDisplay::drawbox( float x, float y, float w, float h, Color fg, Col
|
||||
}
|
||||
|
||||
void TerminalDisplay::drawGrid( const Vector2f& pos ) {
|
||||
const bool subpixelFont = mFontAntialiasing == FontAntialiasing::Subpixel;
|
||||
VertexBuffer* foregroundVBO = subpixelFont ? nullptr : mVBForeground.get();
|
||||
|
||||
if ( mFrameBuffer ) {
|
||||
mFrameBuffer->setPosition( mPosition.floor() + Vector2f( mPadding.Left, mPadding.Top ) );
|
||||
mFrameBuffer->bind();
|
||||
@@ -1313,7 +1316,7 @@ void TerminalDisplay::drawGrid( const Vector2f& pos ) {
|
||||
if ( pos.y + lineHeight * j > pos.y + mSize.getHeight() )
|
||||
break;
|
||||
|
||||
if ( ( mFrameBuffer || mVBForeground ) && !mDirtyLines[j] ) {
|
||||
if ( ( mFrameBuffer || foregroundVBO ) && !mDirtyLines[j] ) {
|
||||
y += lineHeight;
|
||||
continue;
|
||||
}
|
||||
@@ -1358,8 +1361,8 @@ void TerminalDisplay::drawGrid( const Vector2f& pos ) {
|
||||
auto advanceX = spaceCharAdvanceX * ( isWide ? 2.0f : 1.0f );
|
||||
|
||||
if ( glyph.mode & ATTR_WDUMMY ) {
|
||||
if ( mVBForeground ) {
|
||||
mVBForeground->setQuadColor( mCurGridPos, Color::Transparent );
|
||||
if ( foregroundVBO ) {
|
||||
foregroundVBO->setQuadColor( mCurGridPos, Color::Transparent );
|
||||
dirtyFG = true;
|
||||
}
|
||||
continue;
|
||||
@@ -1367,8 +1370,8 @@ void TerminalDisplay::drawGrid( const Vector2f& pos ) {
|
||||
|
||||
if ( glyph.u == 32 && !( glyph.mode & ( ATTR_UNDERLINE | ATTR_STRUCK ) ) ) {
|
||||
x += advanceX;
|
||||
if ( mVBForeground ) {
|
||||
mVBForeground->setQuadColor( mCurGridPos, Color::Transparent );
|
||||
if ( foregroundVBO ) {
|
||||
foregroundVBO->setQuadColor( mCurGridPos, Color::Transparent );
|
||||
dirtyFG = true;
|
||||
}
|
||||
continue;
|
||||
@@ -1377,8 +1380,8 @@ void TerminalDisplay::drawGrid( const Vector2f& pos ) {
|
||||
if ( glyph.mode & ATTR_BOXDRAW ) {
|
||||
auto bd = TerminalEmulator::boxdrawindex( &glyph );
|
||||
drawbox( x, y, advanceX, lineHeight, fg, bg, bd );
|
||||
if ( mVBForeground ) {
|
||||
mVBForeground->setQuadColor( mCurGridPos, Color::Transparent );
|
||||
if ( foregroundVBO ) {
|
||||
foregroundVBO->setQuadColor( mCurGridPos, Color::Transparent );
|
||||
dirtyFG = true;
|
||||
}
|
||||
} else {
|
||||
@@ -1399,8 +1402,8 @@ void TerminalDisplay::drawGrid( const Vector2f& pos ) {
|
||||
gd->setDrawMode( glyph.mode & ATTR_ITALIC ? GlyphDrawable::DrawMode::TextItalic
|
||||
: GlyphDrawable::DrawMode::Text );
|
||||
|
||||
if ( mVBForeground ) {
|
||||
gd->drawIntoVertexBuffer( mVBForeground.get(), mCurGridPos, { x, y } );
|
||||
if ( foregroundVBO ) {
|
||||
gd->drawIntoVertexBuffer( foregroundVBO, mCurGridPos, { x, y } );
|
||||
} else {
|
||||
gd->draw( { x, y } );
|
||||
}
|
||||
@@ -1515,13 +1518,13 @@ void TerminalDisplay::drawGrid( const Vector2f& pos ) {
|
||||
}
|
||||
}
|
||||
|
||||
if ( mVBForeground ) {
|
||||
if ( foregroundVBO ) {
|
||||
mFont->getTexture( mFontSize )->bind();
|
||||
if ( dirtyFG )
|
||||
mVBForeground->update( VERTEX_FLAGS_DEFAULT, false );
|
||||
mVBForeground->bind();
|
||||
mVBForeground->draw();
|
||||
mVBForeground->unbind();
|
||||
foregroundVBO->update( VERTEX_FLAGS_DEFAULT, false );
|
||||
foregroundVBO->bind();
|
||||
foregroundVBO->draw();
|
||||
foregroundVBO->unbind();
|
||||
}
|
||||
|
||||
if ( !mVBStyles.empty() ) {
|
||||
@@ -1841,6 +1844,28 @@ void TerminalDisplay::setFont( Font* font ) {
|
||||
}
|
||||
}
|
||||
|
||||
FontHinting TerminalDisplay::getFontHinting() const {
|
||||
return mFontHinting;
|
||||
}
|
||||
|
||||
void TerminalDisplay::setFontHinting( FontHinting fontHinting ) {
|
||||
if ( mFontHinting != fontHinting ) {
|
||||
mFontHinting = fontHinting;
|
||||
invalidateLines();
|
||||
}
|
||||
}
|
||||
|
||||
FontAntialiasing TerminalDisplay::getFontAntialiasing() const {
|
||||
return mFontAntialiasing;
|
||||
}
|
||||
|
||||
void TerminalDisplay::setFontAntialiasing( FontAntialiasing fontAntialiasing ) {
|
||||
if ( mFontAntialiasing != fontAntialiasing ) {
|
||||
mFontAntialiasing = fontAntialiasing;
|
||||
invalidateLines();
|
||||
}
|
||||
}
|
||||
|
||||
const Float& TerminalDisplay::getFontSize() const {
|
||||
return mFontSize;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <eepp/graphics/fonttruetype.hpp>
|
||||
#include <eepp/scene/scenemanager.hpp>
|
||||
#include <eepp/system/luapattern.hpp>
|
||||
#include <eepp/ui/uieventdispatcher.hpp>
|
||||
@@ -82,6 +83,7 @@ UITerminal::UITerminal( const std::shared_ptr<TerminalDisplay>& terminalDisplay
|
||||
mFlags |= UI_TAB_STOP | UI_SCROLLABLE;
|
||||
if ( !terminalDisplay )
|
||||
return;
|
||||
syncFontRenderingConfig();
|
||||
registerNewTerminal();
|
||||
mVScroll->setParent( this );
|
||||
mVScroll->on( Event::OnValueChange, [this]( const Event* ) { updateScroll(); } );
|
||||
@@ -183,7 +185,9 @@ int UITerminal::getVisibleArea() const {
|
||||
void UITerminal::updateScrollPosition() {
|
||||
if ( mTerm && mTerm->getTerminal() ) {
|
||||
int historySize = mTerm->getTerminal()->getHistorySize();
|
||||
Float val = historySize > 0 ? ( 1.f - mTerm->getTerminal()->scrollPos() / (Float)historySize ) : 1.f;
|
||||
Float val = historySize > 0
|
||||
? ( 1.f - mTerm->getTerminal()->scrollPos() / (Float)historySize )
|
||||
: 1.f;
|
||||
mVScroll->setValue( val, false );
|
||||
}
|
||||
}
|
||||
@@ -366,6 +370,19 @@ Font* UITerminal::getFont() const {
|
||||
|
||||
void UITerminal::setFont( Font* font ) {
|
||||
mTerm->setFont( font );
|
||||
syncFontRenderingConfig();
|
||||
}
|
||||
|
||||
void UITerminal::syncFontRenderingConfig() {
|
||||
if ( !mTerm || !getUISceneNode() || !getUISceneNode()->getResourceScope() )
|
||||
return;
|
||||
const FontService* fontService = nullptr;
|
||||
if ( mTerm->getFont() && mTerm->getFont()->getType() == FontType::TTF )
|
||||
fontService = static_cast<FontTrueType*>( mTerm->getFont() )->getFontService();
|
||||
if ( !fontService )
|
||||
fontService = &getUISceneNode()->getResourceScope()->getFontService();
|
||||
mTerm->setFontHinting( fontService->getHinting() );
|
||||
mTerm->setFontAntialiasing( fontService->getAntialiasing() );
|
||||
}
|
||||
|
||||
void UITerminal::setKeyBindings( const KeyBindings& keyBindings ) {
|
||||
@@ -615,6 +632,7 @@ void UITerminal::restart() {
|
||||
mTerm->getProgram(), mTerm->getArgs(), mTerm->getWorkingDir(),
|
||||
mTerm->getHistorySize(), nullptr, mTerm->useFrameBuffer(),
|
||||
mTerm->getKeepAlive(), mTerm->getEnv() );
|
||||
syncFontRenderingConfig();
|
||||
}
|
||||
|
||||
}} // namespace eterm::UI
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <eepp/graphics/fontfamily.hpp>
|
||||
#include <eepp/graphics/fontsprite.hpp>
|
||||
#include <eepp/graphics/fonttruetype.hpp>
|
||||
#include <eepp/graphics/framebuffer.hpp>
|
||||
#include <eepp/graphics/globalbatchrenderer.hpp>
|
||||
#include <eepp/graphics/image.hpp>
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
@@ -22,6 +23,7 @@
|
||||
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
|
||||
#include <eepp/ui/uiapplication.hpp>
|
||||
#include <eepp/ui/uicodeeditor.hpp>
|
||||
#include <eepp/ui/uiicon.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uitextedit.hpp>
|
||||
#include <eepp/ui/uitextview.hpp>
|
||||
@@ -74,6 +76,180 @@ UTEST( FontRendering, glyphAdvanceDoesNotCreateTexturePages ) {
|
||||
EXPECT_EQ( textureCount, textureFactory->getTextureCount() );
|
||||
}
|
||||
|
||||
UTEST( FontRendering, subpixelCoverageCompositesPerChannel ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 360, 220, "eepp - Subpixel Text Test", WindowStyle::Default,
|
||||
WindowBackend::Default, 32 ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
ResourceScope& scope = *app.getUI()->getResourceScope();
|
||||
FontTrueTypePtr font = FontTrueType::New( "SubpixelText-Regular", scope );
|
||||
ASSERT_TRUE(
|
||||
font->loadFromFile( Sys::getProcessPath() + "../assets/fonts/NotoSans-Regular.ttf" ) );
|
||||
const Float grayscaleLAdvance = font->getGlyphAdvance( 'l', 28 );
|
||||
const Float grayscaleDAdvance = font->getGlyphAdvance( 'd', 28 );
|
||||
const Float grayscaleKerning = font->getKerning( 'i', 'd', 28, false, false );
|
||||
UIIconPtr icon = UIGlyphIcon::New( "glyph-cache-test", font.get(), 'S' );
|
||||
const DrawablePtr grayscaleIcon = icon->getSource( 28 );
|
||||
ASSERT_TRUE( grayscaleIcon );
|
||||
ASSERT_EQ( GlyphRenderMode::Mask,
|
||||
static_cast<GlyphDrawable*>( grayscaleIcon.get() )->getGlyphRenderMode() );
|
||||
font->setAntialiasing( FontAntialiasing::Subpixel );
|
||||
EXPECT_EQ( grayscaleLAdvance, font->getGlyphAdvance( 'l', 28 ) );
|
||||
EXPECT_EQ( grayscaleDAdvance, font->getGlyphAdvance( 'd', 28 ) );
|
||||
EXPECT_EQ( grayscaleKerning, font->getKerning( 'i', 'd', 28, false, false ) );
|
||||
const DrawablePtr subpixelIcon = icon->getSource( 28 );
|
||||
ASSERT_TRUE( subpixelIcon );
|
||||
EXPECT_NE( grayscaleIcon.get(), subpixelIcon.get() );
|
||||
EXPECT_EQ( GlyphRenderMode::Subpixel,
|
||||
static_cast<GlyphDrawable*>( subpixelIcon.get() )->getGlyphRenderMode() );
|
||||
|
||||
GlyphDrawable* drawable = font->getGlyphDrawable( 'S', 28 );
|
||||
ASSERT_TRUE( drawable );
|
||||
ASSERT_EQ( GlyphRenderMode::Subpixel, drawable->getGlyphRenderMode() );
|
||||
|
||||
EE::Window::Window* window = app.getWindow();
|
||||
window->setClearColor( Color::White );
|
||||
window->clear();
|
||||
Text::draw( String( "Subpixel static" ), { 8.f, 4.f }, font.get(), 28, Color::Black );
|
||||
|
||||
Text retained( "Subpixel retained", font.get(), 28 );
|
||||
retained.setFillColor( Color::Black );
|
||||
retained.draw( 8.f, 52.f );
|
||||
|
||||
Primitives primitives;
|
||||
primitives.setColor( Color( 40, 42, 54 ) );
|
||||
primitives.drawRectangle( Rectf( Vector2f( 0.f, 110.f ), Sizef( 360.f, 110.f ) ) );
|
||||
const Color lightText( 248, 248, 242 );
|
||||
Text::draw( String( "Subpixel static light" ), { 8.f, 114.f }, font.get(), 28, lightText );
|
||||
retained.setString( "Subpixel retained light" );
|
||||
retained.setFillColor( lightText );
|
||||
retained.draw( 8.f, 162.f );
|
||||
|
||||
Image image = window->getFrontBufferImage();
|
||||
auto hasColoredCoverage = [&image]( Uint32 top, Uint32 bottom ) {
|
||||
for ( Uint32 y = top; y < bottom; ++y ) {
|
||||
for ( Uint32 x = 0; x < image.getWidth(); ++x ) {
|
||||
Color pixel = image.getPixel( x, y );
|
||||
const Int32 redGreenDelta = static_cast<Int32>( pixel.r ) - pixel.g;
|
||||
const Int32 greenBlueDelta = static_cast<Int32>( pixel.g ) - pixel.b;
|
||||
if ( ( redGreenDelta < 0 ? -redGreenDelta : redGreenDelta ) > 3 ||
|
||||
( greenBlueDelta < 0 ? -greenBlueDelta : greenBlueDelta ) > 3 )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
EXPECT_TRUE_MSG( hasColoredCoverage( 0, image.getHeight() / 2 ),
|
||||
"Static text lost independent LCD channel coverage" );
|
||||
EXPECT_TRUE_MSG( hasColoredCoverage( image.getHeight() / 2, image.getHeight() ),
|
||||
"Retained text lost independent LCD channel coverage" );
|
||||
compareImages( utest_state, utest_result, window, "eepp-subpixel-text" );
|
||||
|
||||
FrameBufferUniquePtr frameBuffer = FrameBuffer::New( 240, 48, false, false, false, 4, window );
|
||||
ASSERT_TRUE( frameBuffer && frameBuffer->created() );
|
||||
frameBuffer->setClearColor( ColorAf( 0.f, 0.f, 0.f, 0.f ) );
|
||||
frameBuffer->bind();
|
||||
frameBuffer->clear();
|
||||
Text::draw( String( "Transparent subpixel" ), { 4.f, 4.f }, font.get(), 28, Color::White );
|
||||
GlobalBatchRenderer::instance()->draw();
|
||||
std::vector<Uint8> pixels( frameBuffer->getWidth() * frameBuffer->getHeight() * 4 );
|
||||
GLi->readPixels( 0, 0, frameBuffer->getWidth(), frameBuffer->getHeight(), pixels.data() );
|
||||
frameBuffer->unbind();
|
||||
bool hasCoverageAlpha = false;
|
||||
for ( size_t i = 3; i < pixels.size(); i += 4 ) {
|
||||
if ( pixels[i] != 0 ) {
|
||||
hasCoverageAlpha = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE_MSG( hasCoverageAlpha,
|
||||
"Subpixel text did not update a transparent target's alpha" );
|
||||
}
|
||||
|
||||
#if EE_PLATFORM == EE_PLATFORM_LINUX
|
||||
UTEST( FontRendering, subpixelCoverageAllRenderers ) {
|
||||
struct RendererCase {
|
||||
GraphicsLibraryVersion version;
|
||||
const char* name;
|
||||
};
|
||||
static constexpr RendererCase renderers[] = { { GLv_2, "OpenGL 2" },
|
||||
{ GLv_3, "OpenGL 3" },
|
||||
{ GLv_3CP, "OpenGL 3 Core" },
|
||||
{ GLv_ES2, "OpenGL ES 2" } };
|
||||
|
||||
for ( const RendererCase& renderer : renderers ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 320, 96, renderer.name, WindowStyle::Default, WindowBackend::Default,
|
||||
32 ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ),
|
||||
ContextSettings( false, 0, 0, renderer.version ) );
|
||||
ASSERT_TRUE_MSG( app.getWindow() && app.getWindow()->isOpen(), renderer.name );
|
||||
ResourceScope& scope = *app.getUI()->getResourceScope();
|
||||
FontTrueTypePtr font = FontTrueType::New( renderer.name, scope );
|
||||
ASSERT_TRUE_MSG(
|
||||
font->loadFromFile( Sys::getProcessPath() + "../assets/fonts/NotoSans-Regular.ttf" ),
|
||||
renderer.name );
|
||||
font->setAntialiasing( FontAntialiasing::Subpixel );
|
||||
|
||||
EE::Window::Window* window = app.getWindow();
|
||||
window->setClearColor( Color::White );
|
||||
window->clear();
|
||||
Text::draw( String( "Direct LCD" ), { 8.f, 4.f }, font.get(), 24, Color::Black );
|
||||
Text retained( "Retained LCD", font.get(), 24 );
|
||||
retained.setFillColor( Color::Black );
|
||||
retained.draw( 8.f, 44.f );
|
||||
|
||||
Image image = window->getFrontBufferImage();
|
||||
auto hasColoredCoverage = [&image]( Uint32 top, Uint32 bottom ) {
|
||||
for ( Uint32 y = top; y < bottom; ++y ) {
|
||||
for ( Uint32 x = 0; x < image.getWidth(); ++x ) {
|
||||
const Color pixel = image.getPixel( x, y );
|
||||
const Int32 redGreenDelta = static_cast<Int32>( pixel.r ) - pixel.g;
|
||||
const Int32 greenBlueDelta = static_cast<Int32>( pixel.g ) - pixel.b;
|
||||
if ( ( redGreenDelta < 0 ? -redGreenDelta : redGreenDelta ) > 3 ||
|
||||
( greenBlueDelta < 0 ? -greenBlueDelta : greenBlueDelta ) > 3 )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
EXPECT_TRUE_MSG( hasColoredCoverage( 0, 40 ), renderer.name );
|
||||
EXPECT_TRUE_MSG( hasColoredCoverage( 40, image.getHeight() ), renderer.name );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
UTEST( FontRendering, scaledSubpixelGlyphAtlas ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 256, 64, "eepp - Scaled Subpixel Glyph Atlas", VisualTestWindowStyle,
|
||||
WindowBackend::Default, 32 ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||
ResourceScope& scope = *app.getUI()->getResourceScope();
|
||||
FontTrueTypePtr font = FontTrueType::New( "ScaledSubpixelNonicons", scope );
|
||||
ASSERT_TRUE( font->loadFromFile( Sys::getProcessPath() + "../assets/fonts/nonicons.ttf" ) );
|
||||
font->setAntialiasing( FontAntialiasing::Subpixel );
|
||||
font->setIsEmojiFont( true );
|
||||
|
||||
EE::Window::Window* window = app.getWindow();
|
||||
window->setClearColor( Color( 40, 44, 52 ) );
|
||||
window->clear();
|
||||
const std::array<Uint32, 8> codePoints = { 61718, 61719, 61720, 61743,
|
||||
61752, 61775, 61789, 61799 };
|
||||
Float x = 8.f;
|
||||
for ( Uint32 codePoint : codePoints ) {
|
||||
GlyphDrawable* glyph = font->getGlyphDrawable( codePoint, 18 );
|
||||
ASSERT_TRUE( glyph );
|
||||
ASSERT_EQ( GlyphRenderMode::Subpixel, glyph->getGlyphRenderMode() );
|
||||
const Sizef size = glyph->getPixelsSize();
|
||||
glyph->setColor( Color::White );
|
||||
glyph->draw( { std::trunc( x + ( 24.f - size.getWidth() ) * 0.5f ),
|
||||
std::trunc( ( 64.f - size.getHeight() ) * 0.5f ) } );
|
||||
x += 30.f;
|
||||
}
|
||||
compareImages( utest_state, utest_result, window, "eepp-scaled-subpixel-glyph-atlas" );
|
||||
}
|
||||
|
||||
UTEST( FontRendering, loadingFontFamilyDoesNotCreateTexturePages ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 320, 240, "eepp - Font Family Metrics Test", WindowStyle::Default,
|
||||
|
||||
@@ -58,6 +58,30 @@ static bool readHttpRequestHeaders( TcpSocket& client, std::string* headers = nu
|
||||
return true;
|
||||
}
|
||||
|
||||
UTEST( UIWebView, DocumentSceneInheritsHostFontRenderingPolicy ) {
|
||||
auto win = Engine::instance()->createWindow(
|
||||
WindowSettings( 320, 240, "UIWebView Font Policy Test", WindowStyle::Default,
|
||||
WindowBackend::Default, 32, {}, 1, false, true ),
|
||||
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||
ASSERT_TRUE( win != nullptr );
|
||||
|
||||
UISceneNode* sceneNode = UISceneNode::New();
|
||||
FontService& hostFontService = sceneNode->getResourceScope()->getFontService();
|
||||
hostFontService.setHinting( FontHinting::Slight );
|
||||
hostFontService.setAntialiasing( FontAntialiasing::Subpixel );
|
||||
SceneManager::instance()->add( sceneNode );
|
||||
|
||||
UIWebView* webView = UIWebView::New();
|
||||
webView->setParent( sceneNode->getRoot() );
|
||||
UISceneNode* documentScene = webView->getDocumentSceneNode();
|
||||
ASSERT_TRUE( documentScene != nullptr );
|
||||
const FontService& documentFontService = documentScene->getResourceScope()->getFontService();
|
||||
EXPECT_EQ( FontHinting::Slight, documentFontService.getHinting() );
|
||||
EXPECT_EQ( FontAntialiasing::Subpixel, documentFontService.getAntialiasing() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIWebView, OwnedDocumentSceneScrollTarget ) {
|
||||
auto win = Engine::instance()->createWindow(
|
||||
WindowSettings( 640, 480, "UIWebView Document Scene Test", WindowStyle::Default,
|
||||
|
||||
@@ -843,6 +843,8 @@ bool App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s
|
||||
|
||||
mConfig.load( mConfigPath, mKeybindingsPath, mInitColorScheme, mRecentFiles, mRecentFolders,
|
||||
mResPath, mPluginManager.get(), displaySize.asInt(), sync );
|
||||
defaultResourceScope().getFontService().setHinting( mConfig.ui.fontHinting );
|
||||
defaultResourceScope().getFontService().setAntialiasing( mConfig.ui.fontAntialiasing );
|
||||
|
||||
return firstRun;
|
||||
}
|
||||
|
||||
@@ -3164,6 +3164,7 @@ UIMenu* SettingsMenu::createFontHintMenu() {
|
||||
auto hint = id.substr( 5 ).toUtf8();
|
||||
mApp->getConfig().ui.fontHinting = FontTrueType::fontHintingFromString( hint );
|
||||
defaultResourceScope().getFontService().setHinting( mApp->getConfig().ui.fontHinting );
|
||||
forEachTerminal( []( UITerminal* term ) { term->syncFontRenderingConfig(); } );
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -3189,7 +3190,7 @@ UIMenu* SettingsMenu::createFontAntiAliasingMenu() {
|
||||
mFontAntiAliasingMenu->addRadioButton( i18n( "none", "None" ) )->setId( "aa_none" );
|
||||
mFontAntiAliasingMenu->addRadioButton( i18n( "grayscale", "Grayscale" ) )
|
||||
->setId( "aa_grayscale" );
|
||||
mFontAntiAliasingMenu->addRadioButton( i18n( "subpixel", "SubPixel (not working)" ) )
|
||||
mFontAntiAliasingMenu->addRadioButton( i18n( "subpixel", "SubPixel" ) )
|
||||
->setId( "aa_subpixel" );
|
||||
}
|
||||
|
||||
@@ -3206,6 +3207,7 @@ UIMenu* SettingsMenu::createFontAntiAliasingMenu() {
|
||||
FontTrueType::fontAntialiasingFromString( hint );
|
||||
defaultResourceScope().getFontService().setAntialiasing(
|
||||
mApp->getConfig().ui.fontAntialiasing );
|
||||
forEachTerminal( []( UITerminal* term ) { term->syncFontRenderingConfig(); } );
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <eepp/ee.hpp>
|
||||
#include <eterm/terminal/terminaldisplay.hpp>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
EE::Window::Window* win = NULL;
|
||||
std::shared_ptr<TerminalDisplay> terminal = nullptr;
|
||||
@@ -176,6 +177,23 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
|
||||
args::ValueFlag<std::string> fallbackFontPathF( parser, "fallback-fontpath",
|
||||
"Fallback Font path", { "fallback-font" } );
|
||||
args::ValueFlag<Float> fontSize( parser, "fontsize", "Font size (in dp)", { "fontsize" }, 11 );
|
||||
const std::unordered_map<std::string, FontHinting> fontHintingMap{
|
||||
{ "none", FontHinting::None },
|
||||
{ "slight", FontHinting::Slight },
|
||||
{ "full", FontHinting::Full },
|
||||
};
|
||||
args::MapFlag<std::string, FontHinting> fontHinting(
|
||||
parser, "font-hinting", "Font hinting mode (accepted values: none, slight, full)",
|
||||
{ "font-hinting" }, fontHintingMap, FontHinting::Full );
|
||||
const std::unordered_map<std::string, FontAntialiasing> fontAntialiasingMap{
|
||||
{ "none", FontAntialiasing::None },
|
||||
{ "grayscale", FontAntialiasing::Grayscale },
|
||||
{ "subpixel", FontAntialiasing::Subpixel },
|
||||
};
|
||||
args::MapFlag<std::string, FontAntialiasing> fontAntialiasing(
|
||||
parser, "font-antialiasing",
|
||||
"Font antialiasing mode (accepted values: none, grayscale, subpixel)",
|
||||
{ "font-antialiasing" }, fontAntialiasingMap, FontAntialiasing::Grayscale );
|
||||
args::ValueFlag<Float> width( parser, "winwidth", "Window width (in dp)", { "width" }, 1280 );
|
||||
args::ValueFlag<Float> height( parser, "winheight", "Window height (in dp)", { "height" },
|
||||
720 );
|
||||
@@ -258,6 +276,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
|
||||
displayManager->enableScreenSaver();
|
||||
displayManager->enableMouseFocusClickThrough();
|
||||
displayManager->disableBypassCompositor();
|
||||
defaultResourceScope().getFontService().setHinting( fontHinting.Get() );
|
||||
defaultResourceScope().getFontService().setAntialiasing( fontAntialiasing.Get() );
|
||||
|
||||
Sizei winSize( width.Get(), height.Get() );
|
||||
win = Engine::instance()->createWindow(
|
||||
@@ -326,6 +346,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
|
||||
MemoryManager::showResults();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
terminal->setFontHinting( fontHinting.Get() );
|
||||
terminal->setFontAntialiasing( fontAntialiasing.Get() );
|
||||
|
||||
terminal->getTerminal()->setAllowMemoryTrimnming( true );
|
||||
terminal->setCursorMode( cursorStyle.Get() );
|
||||
|
||||
Reference in New Issue
Block a user