Compare commits

...

60 commits

Author SHA1 Message Date
Hermet Park
3be197c53a wg_engine: clean code++
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
2025-06-14 12:00:02 +09:00
Sergii Liebodkin
85c1ec4281
wg_engine: hotfix
- initialize stage buffers before drawing
- confirm the surface size in sync stage

Co-authored-by: Jinny You <jinny@lottiefiles.com>
2025-06-14 11:25:40 +09:00
Jinny You
fd1ef5f7ec wg_engine: remove dead condition in target method
Remove redundant instance/device/target checks in the second condition, since they are already early-exited in the first condition.
2025-06-13 23:04:02 +03:00
Hermet Park
482dd0e6f8 renderer: precise condition check for skip compositions
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
single child picture would not also requre composition in a scene
2025-06-13 23:50:35 +09:00
Hermet Park
830db9ecb7 sw_engine: code refactoring
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
- removed a redundant code, sync() will take over.
- deferred the sync as possible as later
2025-06-13 15:43:06 +09:00
Sergii Liebodkin
ed93570756 wg_engine: remove unnecessary mesh pools
remove unnecessary mesh pools
2025-06-13 11:37:34 +09:00
Mira Grudzinska
27e78095ff lottie: fix offset with miter join
One point was skipped during the creation of the offset
corner. The error was not visible because the point lies
on the line, but it will become apparent if further
modifiers are applied to the object (not supported now).
2025-06-13 11:36:22 +09:00
Hermet Park
60f0f4cbe4 Revert "sw_engine: replaced texture mapping AA with 4x sampling interp"
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
This reverts commit 174fae9089.

Will revisit this since the quality is too poor...
2025-06-12 23:40:18 +09:00
Hermet Park
d5a5e3215c renderer: hotfix
added exceptional handling
2025-06-12 23:04:16 +09:00
Sergii Liebodkin
92e3c243ec wg_engine: uniform stage buffers implementation
Introduced stage buffer for uniforms to reduce number of memory shafles from cpu to gpu memory

https://github.com/thorvg/thorvg/issues/3505
2025-06-12 21:57:24 +09:00
Mira Grudzinska
4c3c5d9d06 lottie: readability++
Introduce the _colinear function - checks if a Bezier curve
is degenerated to a line.
2025-06-12 18:48:07 +09:00
Hermet Park
8f10f45756 renderer: caching the viewport update
reuse cached scene viewport data if scene had no update before,
just a minor optimization
2025-06-12 18:44:07 +09:00
Hermet Park
3eaf110e0a sw_engine: revised the texture clipping
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
previous logic doesn't work if the clipping shape
has any inner corners. replace the logic
with a intermediate composition approach for
stability.

- performance can be drop at texture clipping by ~11%
- size is reduced by -0.5kb

issue: https://github.com/thorvg/thorvg/issues/3520
2025-06-11 22:35:54 +09:00
Hermet Park
174fae9089 sw_engine: replaced texture mapping AA with 4x sampling interp
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
The old approach often produced incorrect results,
especially when the fixed pixel had a noticeably
different color from the texture due to the AA
target blending position was fixed.

Although the previous method worked well as an analytical AA
solution with good speed and fair quality, it couldn't
overcome the above limitation.

The new approach still applies AA only to polygon edges
for efficiency. While the quality may be slightly reduced,
it offers greater stability.

- binary size: -1.1kb
- performance diff: ignoreable

issue: https://github.com/thorvg/thorvg/issues/1729
2025-06-11 10:09:39 +09:00
Hermet Park
596f7f767f renderer: chores++
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
2025-06-11 01:49:36 +09:00
Hermet Park
2e5af58592 sw_engine: texture mapping performance optimization
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
- Replaced `modff()` with a custom method,
boosting texture mapping performance by ~15%.

- Unified opacity/non-opacity logic for improved
binary size efficiency(-0.5kb).

- Implemented minor changes for better cache effectiveness.
2025-06-10 19:38:20 +09:00
Hermet Park
bfef89858f renderer: increased the reference count capacity
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
255 is enough size in general but a bit limited.
2025-06-10 14:51:19 +09:00
Hermet Park
b3d73e1568 lottie: do not try matting if the layer has no contents
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
2025-06-09 22:33:25 +09:00
Hermet Park
a2665cbab7 lottie: fixed a repeater opacity logic
preserve the target opacity by multiplying,
do not overwrite it.
2025-06-09 18:52:05 +09:00
Hermet Park
6c1b388d77 docs: updated contributors 2025-06-09 17:04:11 +09:00
Sungun No
676a465c55 gl_engine: fix always-true clear flag
The clear flag specified in Canvas::draw is ignored when set to false,
since GlRenderer::mClearBuffer is never explicitly reset to false.

This commit ensures that mClearBuffer is reset to its default (false)
after being used, so that the clear operation behaves correctly per frame.

- Issue: #1779
2025-06-09 17:02:56 +09:00
Hermet Park
8eb046c318 sw_engine: skip AA if texture are orthogonally rotated.
there is no necessary applying AA for 0/90/180/270 degree rotation

issue: https://github.com/thorvg/thorvg/issues/3452
2025-06-09 16:07:45 +09:00
Hermet Park
ae97f5f20f docs: corrected wrong info
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
2025-06-09 10:54:53 +09:00
Jinny You
dfc7d268a1 infra(ios): Set cpu family to aarch64 for meson compatibility
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
Use meson's official cpu_family name 'aarch64' instead of 'arm64' for ios build.
2025-06-08 11:54:11 +09:00
Hermet Park
eb76769dcf jpg: removed setjmp usage which is not portable with rust 2025-06-08 11:54:08 +09:00
Hermet Park
0fa5d41c8d doc: replaced svg sample shot
Some checks failed
Android / build_x86_64 (push) Has been cancelled
Android / build_aarch64 (push) Has been cancelled
iOS / build_x86_64 (push) Has been cancelled
iOS / build_arm64 (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS / compact_test (push) Has been cancelled
macOS / unit_test (push) Has been cancelled
Ubuntu / build (push) Has been cancelled
Ubuntu / compact_test (push) Has been cancelled
Ubuntu / unit_test (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows / compact_test (push) Has been cancelled
Windows / unit_test (push) Has been cancelled
issue: https://github.com/thorvg/thorvg/issues/3499
2025-06-03 11:32:07 +09:00
Hermet Park
92070fe0de examples: replaced the Lenna with free images
https://www.pexels.com/photo/lovebirds-cuddling-on-a-wooden-branch-30518529/
https://www.pexels.com/@hardeep/

issue: https://github.com/thorvg/thorvg/issues/3499
2025-06-03 11:26:00 +09:00
Hermet Park
6fd7b87754 sw_engine: clean code++
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
2025-06-03 00:58:29 +09:00
Hermet Park
eeebfbb654 sw_engine: hotfix++
resolved a memory violation introduced by:
e2909dd6a4
2025-06-02 22:40:43 +09:00
Hermet Park
dc59440744
Update CODEOWNERS
Some checks failed
Android / build_x86_64 (push) Has been cancelled
Android / build_aarch64 (push) Has been cancelled
iOS / build_x86_64 (push) Has been cancelled
iOS / build_arm64 (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS / compact_test (push) Has been cancelled
macOS / unit_test (push) Has been cancelled
Ubuntu / build (push) Has been cancelled
Ubuntu / compact_test (push) Has been cancelled
Ubuntu / unit_test (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows / compact_test (push) Has been cancelled
Windows / unit_test (push) Has been cancelled
Removed inactive maintaining parts.
2025-05-31 01:24:36 +09:00
Sergii Liebodkin
24509b0e41 wg_engine: geometry stage buffers implementation
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
Implemented task-based rendering and geometry stage buffers:
1. Get information about current frame objects
2. Accumulate geometry data into a stage buffer during frame rendering
3. Flush it to the GPU in single call
4. Run rendering process in post render stage

https://github.com/thorvg/thorvg/issues/3489
https://github.com/thorvg/thorvg/issues/3455
2025-05-30 02:21:31 +09:00
Hermet Park
1f53f2d72f capi: code refactoring
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
begin with a positive path for optimal instruction flow
at the machine code level.
2025-05-29 15:30:59 +09:00
Hermet Park
2eb2b83bb0 api: revise the clip() apis.
- Paint explicity allows a shape as a clipper
- Added clipper getter apis

API Updates
 + Shape* Paint::clip()
 * Result Paint::clip(Paint* clipper) -> Result Paint::clip(Shape* clipper)

CAPI Updates
 + Tvg_Paint* tvg_paint_get_clip(const Tvg_Paint* paint)
 * Tvg_Result tvg_paint_clip(Tvg_Paint* paint, Tvg_Paint* clipper)
   -> Tvg_Result tvg_paint_set_clip(Tvg_Paint* paint, Tvg_Paint* clipper);

Please note that clipper type can be changed again to tvg::Path
in the upcoming update.
2025-05-29 14:02:32 +09:00
Hermet Park
e2909dd6a4 sw_engine: replace RLE memory with common array
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
This commit has two purposes:

- refactoring to introduce y indexing method for the upcoming partial rendering.
- replaces the RLE-specific memory allocation with a shared array structure,
eliminating potential memory overflows during RLE clipping.
2025-05-28 23:17:18 +09:00
Sergii Liebodkin
d0be8cd2bd gl_engine: fix compilation warning with RenderRegion data type 2025-05-28 19:20:47 +09:00
Hermet Park
dc8c5bce50 sw_engine: code refactoring
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
just renamed internal variables (region -> bbox)
for the sake of a shorter name, no logical changes.
2025-05-28 11:41:52 +09:00
Hermet Park
cc72eda465 sw_engine: unify RenderRegion and SwBBox
refactored for smoother data flow through the rendering pipeline.
2025-05-28 11:41:52 +09:00
Hermet Park
8a35f02105 renderer/engines: redesigned RenderRegion property layout
redefiend properties so that min/max are prioritized,
as they are accessed more frequently than pos/size
during rendering calculations.

also introduced miscellaneous functions to improve usability.
2025-05-28 11:41:52 +09:00
Hermet Park
04b7bb4f25 renderer: allow empty content during canvas rendering
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
This allow more generous handling of failure cases for invalid content,
thorvg can draw other successful contents.

Now, only raster engines can return fail cases in the picture.
2025-05-28 00:48:48 +09:00
Hermet Park
b221eed7fa renderer: chores 2025-05-27 17:58:10 +09:00
Mira Grudzinska
211fee73e2 lottie: fix precomposition with masking
Some checks failed
Android / build_x86_64 (push) Has been cancelled
Android / build_aarch64 (push) Has been cancelled
iOS / build_x86_64 (push) Has been cancelled
iOS / build_arm64 (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS / compact_test (push) Has been cancelled
macOS / unit_test (push) Has been cancelled
Ubuntu / build (push) Has been cancelled
Ubuntu / compact_test (push) Has been cancelled
Ubuntu / unit_test (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows / compact_test (push) Has been cancelled
Windows / unit_test (push) Has been cancelled
A precomposition layer is clipped to its viewport.
If the same layer also had a mask that was optimized
using clipping, this clip was unintentionally overridden
by the viewport clipping. This conflict is now fixed.
2025-05-26 19:26:45 +09:00
Sergii Liebodkin
55847bdcb3 gl_engine: fix memoty leak on target resize
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
We must to remove offscreen render buffers during removing render target

https://github.com/thorvg/thorvg/issues/3210
2025-05-25 23:04:12 +09:00
Hermet Park
32c38041db sw_engine: added diagnostics for potential issues
Some checks failed
Android / build_x86_64 (push) Has been cancelled
Android / build_aarch64 (push) Has been cancelled
iOS / build_x86_64 (push) Has been cancelled
iOS / build_arm64 (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS / compact_test (push) Has been cancelled
macOS / unit_test (push) Has been cancelled
Ubuntu / build (push) Has been cancelled
Ubuntu / compact_test (push) Has been cancelled
Ubuntu / unit_test (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows / compact_test (push) Has been cancelled
Windows / unit_test (push) Has been cancelled
2025-05-22 16:12:42 +09:00
Mira Grudzinska
1b72113fcc sw_engine: fix too small memory alloc for spans
In some clipping cases, the memory allocated for storing spans
was too small. As a result, the entire clipped area might not
have been rendered.
This has been resolved by adding an experimental factor to increase
the size of allocated memory.

@issue: https://github.com/thorvg/thorvg/issues/3461
2025-05-22 16:03:04 +09:00
Hermet Park
1d0973cdf0 wasm: enable software only it's valid 2025-05-22 15:34:58 +09:00
Hermet Park
e01ccb5db9 sw_engine: handle an exception ASAP
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
2025-05-21 17:30:32 +09:00
JunsuChoi
c41b2c2a1c svg_loader: Fix <stop> being registered in closed latestGradient
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
If stop is declared immediately after Gradient is closed, the stop is registered in latestGradient.
Change latestGradient to a Stack and add a stop only to the last added gradient.
When that gradient is closed, no more stops should be registered for that gradient.
And gradients do not allow nested declarations, so only the earliest declared Gradient is valid.
2025-05-21 11:41:03 +09:00
Benson Muite
633bcd3176 tools/lottie2gif: typo fix
Some checks failed
Android / build_x86_64 (push) Has been cancelled
Android / build_aarch64 (push) Has been cancelled
iOS / build_x86_64 (push) Has been cancelled
iOS / build_arm64 (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS / compact_test (push) Has been cancelled
macOS / unit_test (push) Has been cancelled
Ubuntu / build (push) Has been cancelled
Ubuntu / compact_test (push) Has been cancelled
Ubuntu / unit_test (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows / compact_test (push) Has been cancelled
Windows / unit_test (push) Has been cancelled
2025-05-16 16:05:18 +09:00
Hermet Park
e98b87892b gl_engine: revert some wrong change
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run
my fault. signed value should be respected
from cd12618529
2025-05-16 02:08:25 +09:00
Hermet Park
97d96a7f8b
Update README.md
added deepwiki
2025-05-16 00:54:23 +09:00
Hermet Park
bace6b9e52 lottie: removed undesired embedded option.
Use local font by default and fall back if no glyphs exist.

Co-Authored-By: Mira Grudzinska <mira@lottiefiles.com>
2025-05-15 19:21:24 +09:00
Jinny You
1e692f223c lottie: handle time remapping with zero value correctly
The time remapping logic had an issue where animations with zero value "tm"(Time Remap) were not being processed correctly.

This was happening because the previous implementation only applied time remapping when the value was non-zero. (A zero-value Time Remap should be applied, but is ignored)

The fix changes the default time remap value to `-1.0` (since frame values cannot be negative) and applies time remapping when the value is 0.0 or greater. This ensures that zero value time remapping is properly processed.
2025-05-14 21:53:32 +09:00
Mira Grudzinska
6e41b44ea1 lottie: fix points calculation for rounded polygon
Added missing factor.

issue: https://github.com/thorvg/thorvg/issues/2629
2025-05-14 21:53:32 +09:00
Hermet Park
cd12618529 common/array: ++neat code
no logical changes
2025-05-14 21:53:32 +09:00
Hermet Park
7e8743e8fe wg_engine: ++exception handling 2025-05-13 16:28:01 +09:00
Hermet Park
5154e491cc wg_engine: ++optimal logic
avoid using initializer here.
2025-05-13 14:47:40 +09:00
Hermet Park
5ed719dcec sw_engine: code clean up
removed unnecesary parameters
2025-05-12 17:37:40 +09:00
Mira Grudzinska
97817bedfe lottie: support for firstMargin < 0 in text follow path
The first margin value shifts the starting point where the text
begins along the path. Now cases where firstMargin < 0 are handled,
for both closed and open paths.
2025-05-12 15:32:32 +09:00
Hermet Park
3fbb55440a renderer: ++engines safety
Added drawing exceptions when target is not properly ready.

Now, Canvas::update() Canvas::draw() will return InsufficientCondition
if the target has not been set beforehand.
2025-05-12 12:49:10 +09:00
Hermet Park
2524cdfee5 Update README.md 2025-05-09 12:51:57 +09:00
84 changed files with 2971 additions and 2807 deletions

View file

@ -4,14 +4,8 @@
* @hermet
/src/renderer/sw_engine @mgrudzinska
/src/renderer/gl_engine @RuiwenTang @SergeyLebedkin
/src/renderer/gl_engine @SergeyLebedkin @RuiwenTang
/src/renderer/wg_engine @SergeyLebedkin
/src/loaders/external_webp @JSUYA
/src/loaders/raw @JSUYA
/src/loaders/svg @JSUYA @mgrudzinska
/src/loaders/webp @JSUYA
/src/loaders/svg @mgrudzinska @JSUYA
/src/loaders/lottie @mgrudzinska
/src/bindings/capi @mgrudzinska
/src/bindings/wasm @tinyjin
/src/savers/gif @JSUYA
/src/tools/svg2png @JSUYA

View file

@ -44,3 +44,4 @@ Thaddeus Crews <repiteo@outlook.com>
Benjamin <benjaminhalko@hotmail.com>
Benson Muite <benson_muite@emailplus.com>
kkocdko <kkocdko@gmail.com>
SoonGeon Noh <nors.nsg@gmail.com>

View file

@ -1,5 +1,6 @@
[![Discord](https://img.shields.io/badge/Community-5865f2?style=flat&logo=discord&logoColor=white)](https://discord.gg/n25xj6J6HM)
[![ThorVGPT](https://img.shields.io/badge/ThorVGPT-76A99C?style=flat&logo=openai&logoColor=white)](https://chat.openai.com/g/g-Ht3dYIwLO-thorvgpt)
[![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/thorvg/thorvg)
[![OpenCollective](https://img.shields.io/badge/OpenCollective-84B5FC?style=flat&logo=opencollective&logoColor=white)](https://opencollective.com/thorvg)
[![License](https://img.shields.io/badge/licence-MIT-green.svg?style=flat)](LICENSE)
![BinarySize](https://img.shields.io/badge/Size->150kb-black)
@ -98,7 +99,7 @@ ThorVG is actively under development, continuously expanding its support for ess
- [LVGL](#lvgl)
- [Tizen](#tizen)
- [References](#references)
- [APIs](#apis)
- [Documentation](#documentation)
- [Examples](#examples)
- [Tools](#tools)
- [ThorVG Viewer](#thorvg-viewer)
@ -294,7 +295,7 @@ ThorVG facilitates [SVG Tiny Specification](https://www.w3.org/TR/SVGTiny12/) re
The figure below highlights ThorVG's SVG rendering capabilities:
<p align="center">
<img width="780" height="auto" src="https://github.com/thorvg/thorvg/blob/main/res/example_svg.png">
<img width="780" height="auto" src="https://github.com/thorvg/thorvg/blob/main/res/example_svg.jpg">
</p>
The following code snippet shows how to draw SVG image using ThorVG:
@ -420,8 +421,10 @@ ThorVG has been integrated into the [Tizen](https://www.tizen.org) platform as t
[Back to contents](#contents)
<br />
<br />
## APIs
The ThorVG API documentation can be accessed at [thorvg.org/apis](https://www.thorvg.org/apis), and is also available in the [C++ API](https://github.com/thorvg/thorvg/blob/main/inc/thorvg.h), [C API](https://github.com/thorvg/thorvg/blob/main/src/bindings/capi/thorvg_capi.h) within this repository.
## Documentation
The ThorVG API documentation is available at [thorvg.org/apis](https://www.thorvg.org/apis), and can also be found directly in this repository via the [C++ API](https://github.com/thorvg/thorvg/blob/main/inc/thorvg.h) and [C API](https://github.com/thorvg/thorvg/blob/main/src/bindings/capi/thorvg_capi.h).
For comprehensive and well-structured technical information, please visit the [DeepWiki](https://deepwiki.com/thorvg/thorvg), which offers in-depth guidance on ThorVG's architecture, features, and usage.
[Back to contents](#contents)
<br />
@ -529,14 +532,16 @@ meson setup builddir -Dbindings="capi"
<br />
<br />
## Dependencies
ThorVG offers versatile support for image loading, accommodating both static and external loaders. This flexibility ensures that, even in environments without external libraries, users can still leverage static loaders as a reliable alternative. At its foundation, the ThorVG core library is engineered to function autonomously, free from external dependencies. However, it is important to note that ThorVG also encompasses a range of optional feature extensions, each with its specific set of dependencies. The dependencies associated with these selective features are outlined as follows:
ThorVG provides flexible image loading capabilities, supporting both static and external loaders. This design ensures that even in environments lacking external libraries, users can rely on built-in static loaders for core functionality. At its core, the ThorVG library is fully self-contained and operates without mandatory external dependencies. However, several optional feature extensions are available, each with its own set of dependencies.
* GL engine: [OpenGL v3.3](https://www.khronos.org/opengl/) or [GLES v3.0](https://www.khronos.org/opengles/)
* WG engine: [webgpu-native](https://github.com/gfx-rs/wgpu-native)
* External PNG support: [libpng](https://github.com/glennrp/libpng)
* External JPG support: [turbojpeg](https://github.com/libjpeg-turbo/libjpeg-turbo)
* External WebP support: [libwebp](https://developers.google.com/speed/webp/download)
* Examples: [SDL2](https://www.libsdl.org/)
The following outlines the dependencies for these optional features:
* **GL Engine**: [OpenGL 3.3](https://www.khronos.org/opengl/), [OpenGL ES 3.0](https://www.khronos.org/opengles/), or a browser with [WebGL2](https://www.khronos.org/webgl/) support.
* **WG Engine**: [webgpu-native v0.22](https://github.com/gfx-rs/wgpu-native) or a browser with [WebGPU](https://www.w3.org/TR/webgpu/) support.
* **PNG Loader** (external): [libpng](https://github.com/pnggroup/libpng)
* **JPEG Loader** (external): [libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo)
* **WebP Loader** (external): [libwebp](https://developers.google.com/speed/webp/download)
* **Examples**: [SDL2](https://www.libsdl.org/)
[Back to contents](#contents)
<br />

View file

@ -17,6 +17,6 @@ cpp_link_args = ['-miphoneos-version-min=11.0']
system = 'darwin'
subsystem = 'ios'
kernel = 'xnu'
cpu_family = 'arm64'
cpu = 'arm64'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'

View file

@ -65,7 +65,7 @@ struct UserExample : tvgexam::Example
auto picture = tvg::Picture::gen();
if (!tvgexam::verify(picture->load(data, size, "png", "", true))) return false;
free(data);
picture->translate(400, 0);
picture->translate(380, 0);
picture->scale(0.8);
canvas->push(picture);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 444 KiB

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 533 KiB

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 617 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View file

@ -65,6 +65,7 @@ namespace tvg
class RenderMethod;
class Animation;
class Shape;
/**
* @defgroup ThorVG ThorVG
@ -229,9 +230,9 @@ enum class BlendMethod : uint8_t
enum class SceneEffect : uint8_t
{
ClearAll = 0, ///< Reset all previously applied scene effects, restoring the scene to its original state.
GaussianBlur, ///< Apply a blur effect with a Gaussian filter. Param(3) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]}
GaussianBlur, ///< Apply a blur effect with a Gaussian filter. Param(4) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]}
DropShadow, ///< Apply a drop shadow effect with a Gaussian Blur filter. Param(8) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255], angle(double)[0 - 360], distance(double), blur_sigma(double)[> 0], quality(int)[0 - 100]}
Fill, ///< Override the scene content color with a given fill information (Experimental API). Param(5) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255]}
Fill, ///< Override the scene content color with a given fill information (Experimental API). Param(4) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255]}
Tint, ///< Tinting the current scene color with a given black, white color paramters (Experimental API). Param(7) = {black_R(int)[0 - 255], black_G(int)[0 - 255], black_B(int)[0 - 255], white_R(int)[0 - 255], white_G(int)[0 - 255], white_B(int)[0 - 255], intensity(float)[0 - 100]}
Tritone ///< Apply a tritone color effect to the scene using three color parameters for shadows, midtones, and highlights (Experimental API). Param(9) = {Shadow_R(int)[0 - 255], Shadow_G(int)[0 - 255], Shadow_B(int)[0 - 255], Midtone_R(int)[0 - 255], Midtone_G(int)[0 - 255], Midtone_B(int)[0 - 255], Highlight_R(int)[0 - 255], Highlight_G(int)[0 - 255], Highlight_B(int)[0 - 255]}
};
@ -396,13 +397,13 @@ public:
*
* @param[in] clipper The shape object as the clipper.
*
* @retval Result::NonSupport If the @p clipper type is not Shape.
* @retval Result::InsufficientCondition if the target has already belonged to another paint.
* @retval Result::InsufficientCondition if the @p clipper has already belonged to another paint.
*
* @see Paint::clip()
*
* @note @p clipper only supports the Shape type.
* @since 1.0
*/
Result clip(Paint* clipper) noexcept;
Result clip(Shape* clipper) noexcept;
/**
* @brief Sets the blending method for the paint object.
@ -478,6 +479,19 @@ public:
*/
MaskMethod mask(const Paint** target) const noexcept;
/**
* @brief Get the clipper shape of the paint object.
*
* This function returns the clipper that has been previously set to this paint object.
*
* @return The shape object used as the clipper, or @c nullptr if no clipper is set.
*
* @see Paint::clip(Shape* clipper)
*
* @since 1.0
*/
Shape* clip() const noexcept;
/**
* @brief Increment the reference count for the Paint instance.
*

BIN
res/example_svg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 818 KiB

View file

@ -972,7 +972,7 @@ TVG_API Tvg_Result tvg_paint_set_mask_method(Tvg_Paint* paint, Tvg_Paint* target
TVG_API Tvg_Result tvg_paint_get_mask_method(const Tvg_Paint* paint, const Tvg_Paint** target, Tvg_Mask_Method* method);
/*!
/**
* @brief Clip the drawing region of the paint object.
*
* This function restricts the drawing area of the paint object to the specified shape's paths.
@ -985,10 +985,24 @@ TVG_API Tvg_Result tvg_paint_get_mask_method(const Tvg_Paint* paint, const Tvg_P
* @retval TVG_RESULT_INSUFFICIENT_CONDITION if the target has already belonged to another paint.
* @retval TVG_RESULT_NOT_SUPPORTED If the @p clipper type is not Shape.
*
* @see tvg_paint_get_clip()
* @since 1.0
*/
TVG_API Tvg_Result tvg_paint_clip(Tvg_Paint* paint, Tvg_Paint* clipper);
TVG_API Tvg_Result tvg_paint_set_clip(Tvg_Paint* paint, Tvg_Paint* clipper);
/**
* @brief Get the clipper shape of the paint object.
*
* This function returns the clipper that has been previously set to this paint object.
*
* @return The shape object used as the clipper, or @c nullptr if no clipper is set.
*
* @see tvg_paint_set_clip()
*
* @since 1.0
*/
TVG_API Tvg_Paint* tvg_paint_get_clip(const Tvg_Paint* paint);
/**
* @brief Retrieves the parent paint object.

File diff suppressed because it is too large Load diff

View file

@ -47,6 +47,8 @@ struct TvgEngineMethod
}
};
#ifdef THORVG_SW_RASTER_SUPPORT
struct TvgSwEngine : TvgEngineMethod
{
uint8_t* buffer = nullptr;
@ -78,6 +80,8 @@ struct TvgSwEngine : TvgEngineMethod
}
};
#endif
#ifdef THORVG_WG_RASTER_SUPPORT
@ -243,12 +247,14 @@ public:
{
errorMsg = NoError;
#ifdef THORVG_SW_RASTER_SUPPORT
if (engine == "sw") this->engine = new TvgSwEngine;
#endif
#ifdef THORVG_GL_RASTER_SUPPORT
else if (engine == "gl") this->engine = new TvgGLEngine;
if (engine == "gl") this->engine = new TvgGLEngine;
#endif
#ifdef THORVG_WG_RASTER_SUPPORT
else if (engine == "wg") this->engine = new TvgWgEngine;
if (engine == "wg") this->engine = new TvgWgEngine;
#endif
if (!this->engine) {

View file

@ -95,6 +95,24 @@ struct Array
return data[idx];
}
void operator=(const Array& rhs)
{
reserve(rhs.count);
if (rhs.count > 0) memcpy(data, rhs.data, sizeof(T) * rhs.count);
count = rhs.count;
}
void move(Array& to)
{
to.reset();
to.data = data;
to.count = count;
to.reserved = reserved;
data = nullptr;
count = reserved = 0;
}
const T* begin() const
{
return data;
@ -130,6 +148,12 @@ struct Array
return data[count - 1];
}
T& next()
{
if (full()) grow(count + 1);
return data[count++];
}
T& first()
{
return data[0];
@ -157,17 +181,14 @@ struct Array
return count == 0;
}
template<class COMPARE>
void sort()
bool full()
{
qsort<COMPARE>(data, 0, static_cast<int32_t>(count) - 1);
return count == reserved;
}
void operator=(const Array& rhs)
template<class COMPARE> void sort()
{
reserve(rhs.count);
if (rhs.count > 0) memcpy(data, rhs.data, sizeof(T) * rhs.count);
count = rhs.count;
qsort<COMPARE>(data, 0, (int32_t)(count - 1));
}
~Array()
@ -180,20 +201,14 @@ private:
void qsort(T* arr, int32_t low, int32_t high)
{
if (low < high) {
int32_t i = low;
int32_t j = high;
T tmp = arr[low];
auto i = low;
auto j = high;
auto tmp = arr[low];
while (i < j) {
while (i < j && !COMPARE{}(arr[j], tmp)) --j;
if (i < j) {
arr[i] = arr[j];
++i;
}
if (i < j) arr[i++] = arr[j];
while (i < j && COMPARE{}(arr[i], tmp)) ++i;
if (i < j) {
arr[j] = arr[i];
--j;
}
if (i < j) arr[j--] = arr[i];
}
arr[i] = tmp;
qsort<COMPARE>(arr, low, i - 1);

View file

@ -90,7 +90,7 @@ bool operator==(const Matrix& lhs, const Matrix& rhs);
static inline bool rightAngle(const Matrix& m)
{
auto radian = fabsf(tvg::atan2(m.e21, m.e11));
if (radian < FLOAT_EPSILON || tvg::equal(radian, MATH_PI2) || tvg::equal(radian, MATH_PI)) return true;
if (tvg::zero(radian) || tvg::zero(radian - MATH_PI2) || tvg::zero(radian - MATH_PI)) return true;
return false;
}

View file

@ -33,21 +33,11 @@
#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>
#include <stdint.h>
#include "tvgCommon.h"
#include "tvgJpgd.h"
#ifdef _MSC_VER
#pragma warning (disable : 4611) // warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable
#define JPGD_NORETURN __declspec(noreturn)
#elif defined(__GNUC__)
#define JPGD_NORETURN __attribute__ ((noreturn))
#else
#define JPGD_NORETURN
#endif
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
@ -171,7 +161,7 @@ private:
jpeg_decoder(const jpeg_decoder &);
jpeg_decoder &operator =(const jpeg_decoder &);
typedef void (*pDecode_block_func)(jpeg_decoder *, int, int, int);
typedef bool (*pDecode_block_func)(jpeg_decoder*, int, int, int);
struct huff_tables
{
@ -198,7 +188,6 @@ private:
char m_data[1];
};
jmp_buf m_jmp_state;
mem_block *m_pMem_blocks;
int m_image_x_size;
int m_image_y_size;
@ -274,21 +263,21 @@ private:
int m_total_bytes_read;
void free_all_blocks();
JPGD_NORETURN void stop_decoding(jpgd_status status);
bool stop_decoding(jpgd_status status);
void *alloc(size_t n, bool zero = false);
void word_clear(void *p, uint16_t c, uint32_t n);
void prep_in_buffer();
void read_dht_marker();
void read_dqt_marker();
void read_sof_marker();
void skip_variable_marker();
void read_dri_marker();
void read_sos_marker();
bool prep_in_buffer();
bool read_dht_marker();
bool read_dqt_marker();
bool read_sof_marker();
bool skip_variable_marker();
bool read_dri_marker();
bool read_sos_marker();
int next_marker();
int process_markers();
void locate_soi_marker();
void locate_sof_marker();
int locate_sos_marker();
bool locate_soi_marker();
bool locate_sof_marker();
bool locate_sos_marker();
void init(jpeg_decoder_stream * pStream);
void create_look_ups();
void fix_in_buffer();
@ -297,18 +286,18 @@ private:
coeff_buf* coeff_buf_open(int block_num_x, int block_num_y, int block_len_x, int block_len_y);
inline jpgd_block_t *coeff_buf_getp(coeff_buf *cb, int block_x, int block_y);
void load_next_row();
void decode_next_row();
bool decode_next_row();
void make_huff_table(int index, huff_tables *pH);
void check_quant_tables();
void check_huff_tables();
bool check_quant_tables();
bool check_huff_tables();
void calc_mcu_block_order();
int init_scan();
void init_frame();
void process_restart();
void decode_scan(pDecode_block_func decode_block_func);
void init_progressive();
void init_sequential();
void decode_start();
bool init_frame();
bool process_restart();
bool decode_scan(pDecode_block_func decode_block_func);
bool init_progressive();
bool init_sequential();
bool decode_start();
void decode_init(jpeg_decoder_stream * pStream);
void H2V2Convert();
void H2V1Convert();
@ -326,10 +315,10 @@ private:
inline int huff_decode(huff_tables *pH);
inline int huff_decode(huff_tables *pH, int& extrabits);
static inline uint8_t clamp(int i);
static void decode_block_dc_first(jpeg_decoder *pD, int component_id, int block_x, int block_y);
static void decode_block_dc_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y);
static void decode_block_ac_first(jpeg_decoder *pD, int component_id, int block_x, int block_y);
static void decode_block_ac_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y);
static bool decode_block_dc_first(jpeg_decoder *pD, int component_id, int block_x, int block_y);
static bool decode_block_dc_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y);
static bool decode_block_ac_first(jpeg_decoder *pD, int component_id, int block_x, int block_y);
static bool decode_block_ac_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y);
};
@ -630,7 +619,7 @@ inline uint32_t jpeg_decoder::get_char()
// Any bytes remaining in buffer?
if (!m_in_buf_left) {
// Try to get more bytes.
prep_in_buffer();
if (!prep_in_buffer()) return 0xFF;
// Still nothing to get?
if (!m_in_buf_left) {
// Pad the end of the stream with 0xFF 0xD9 (EOI marker)
@ -650,7 +639,7 @@ inline uint32_t jpeg_decoder::get_char()
inline uint32_t jpeg_decoder::get_char(bool *pPadding_flag)
{
if (!m_in_buf_left) {
prep_in_buffer();
if (!prep_in_buffer()) return 0xFF;
if (!m_in_buf_left) {
*pPadding_flag = true;
int t = m_tem_flag;
@ -1093,13 +1082,15 @@ void jpeg_decoder::free_all_blocks()
}
// This method handles all errors. It will never return.
// It could easily be changed to use C++ exceptions.
JPGD_NORETURN void jpeg_decoder::stop_decoding(jpgd_status status)
bool jpeg_decoder::stop_decoding(jpgd_status status)
{
#if 0 //for debugging
m_error_code = status;
#else
m_error_code = JPGD_FAILED;
#endif
free_all_blocks();
longjmp(m_jmp_state, status);
return false;
}
@ -1116,8 +1107,7 @@ void *jpeg_decoder::alloc(size_t nSize, bool zero)
}
if (!rv) {
int capacity = JPGD_MAX(32768 - 256, (nSize + 2047) & ~2047);
mem_block *b = tvg::malloc<mem_block*>(sizeof(mem_block) + capacity);
if (!b) stop_decoding(JPGD_NOTENOUGHMEM);
auto b = tvg::malloc<mem_block*>(sizeof(mem_block) + capacity);
b->m_pNext = m_pMem_blocks; m_pMem_blocks = b;
b->m_used_count = nSize;
b->m_size = capacity;
@ -1142,16 +1132,15 @@ void jpeg_decoder::word_clear(void *p, uint16_t c, uint32_t n)
// Refill the input buffer.
// This method will sit in a loop until (A) the buffer is full or (B)
// the stream's read() method reports and end of file condition.
void jpeg_decoder::prep_in_buffer()
bool jpeg_decoder::prep_in_buffer()
{
m_in_buf_left = 0;
m_pIn_buf_ofs = m_in_buf;
if (m_eof_flag) return;
if (m_eof_flag) return true;
do {
int bytes_read = m_pStream->read(m_in_buf + m_in_buf_left, JPGD_IN_BUF_SIZE - m_in_buf_left, &m_eof_flag);
if (bytes_read == -1) stop_decoding(JPGD_STREAM_READ);
auto bytes_read = m_pStream->read(m_in_buf + m_in_buf_left, JPGD_IN_BUF_SIZE - m_in_buf_left, &m_eof_flag);
if (bytes_read == -1) return stop_decoding(JPGD_STREAM_READ);
m_in_buf_left += bytes_read;
} while ((m_in_buf_left < JPGD_IN_BUF_SIZE) && (!m_eof_flag));
@ -1160,18 +1149,19 @@ void jpeg_decoder::prep_in_buffer()
// Pad the end of the block with M_EOI (prevents the decompressor from going off the rails if the stream is invalid).
// (This dates way back to when this decompressor was written in C/asm, and the all-asm Huffman decoder did some fancy things to increase perf.)
word_clear(m_pIn_buf_ofs + m_in_buf_left, 0xD9FF, 64);
return true;
}
// Read a Huffman code table.
void jpeg_decoder::read_dht_marker()
bool jpeg_decoder::read_dht_marker()
{
int i, index, count;
uint8_t huff_num[17];
uint8_t huff_val[256];
uint32_t num_left = get_bits(16);
if (num_left < 2) stop_decoding(JPGD_BAD_DHT_MARKER);
if (num_left < 2) return stop_decoding(JPGD_BAD_DHT_MARKER);
num_left -= 2;
while (num_left) {
@ -1184,19 +1174,19 @@ void jpeg_decoder::read_dht_marker()
count += huff_num[i];
}
if (count > 255) stop_decoding(JPGD_BAD_DHT_COUNTS);
if (count > 255) return stop_decoding(JPGD_BAD_DHT_COUNTS);
for (i = 0; i < count; i++)
huff_val[i] = static_cast<uint8_t>(get_bits(8));
i = 1 + 16 + count;
if (num_left < (uint32_t)i) stop_decoding(JPGD_BAD_DHT_MARKER);
if (num_left < (uint32_t)i) return stop_decoding(JPGD_BAD_DHT_MARKER);
num_left -= i;
if ((index & 0x10) > 0x10) stop_decoding(JPGD_BAD_DHT_INDEX);
if ((index & 0x10) > 0x10) return stop_decoding(JPGD_BAD_DHT_INDEX);
index = (index & 0x0F) + ((index & 0x10) >> 4) * (JPGD_MAX_HUFF_TABLES >> 1);
if (index >= JPGD_MAX_HUFF_TABLES) stop_decoding(JPGD_BAD_DHT_INDEX);
if (index >= JPGD_MAX_HUFF_TABLES) return stop_decoding(JPGD_BAD_DHT_INDEX);
if (!m_huff_num[index]) m_huff_num[index] = (uint8_t *)alloc(17);
if (!m_huff_val[index]) m_huff_val[index] = (uint8_t *)alloc(256);
@ -1205,16 +1195,17 @@ void jpeg_decoder::read_dht_marker()
memcpy(m_huff_num[index], huff_num, 17);
memcpy(m_huff_val[index], huff_val, 256);
}
return true;
}
// Read a quantization table.
void jpeg_decoder::read_dqt_marker()
bool jpeg_decoder::read_dqt_marker()
{
int n, i, prec;
uint32_t temp;
uint32_t num_left = get_bits(16);
if (num_left < 2) stop_decoding(JPGD_BAD_DQT_MARKER);
if (num_left < 2) return stop_decoding(JPGD_BAD_DQT_MARKER);
num_left -= 2;
while (num_left) {
@ -1222,7 +1213,7 @@ void jpeg_decoder::read_dqt_marker()
prec = n >> 4;
n &= 0x0F;
if (n >= JPGD_MAX_QUANT_TABLES) stop_decoding(JPGD_BAD_DQT_TABLE);
if (n >= JPGD_MAX_QUANT_TABLES) return stop_decoding(JPGD_BAD_DQT_TABLE);
if (!m_quant[n]) m_quant[n] = (jpgd_quant_t *)alloc(64 * sizeof(jpgd_quant_t));
@ -1234,30 +1225,31 @@ void jpeg_decoder::read_dqt_marker()
}
i = 64 + 1;
if (prec) i += 64;
if (num_left < (uint32_t)i) stop_decoding(JPGD_BAD_DQT_LENGTH);
if (num_left < (uint32_t)i) return stop_decoding(JPGD_BAD_DQT_LENGTH);
num_left -= i;
}
return true;
}
// Read the start of frame (SOF) marker.
void jpeg_decoder::read_sof_marker()
bool jpeg_decoder::read_sof_marker()
{
int i;
uint32_t num_left = get_bits(16);
if (get_bits(8) != 8) stop_decoding(JPGD_BAD_PRECISION); /* precision: sorry, only 8-bit precision is supported right now */
if (get_bits(8) != 8) return stop_decoding(JPGD_BAD_PRECISION); /* precision: sorry, only 8-bit precision is supported right now */
m_image_y_size = get_bits(16);
if ((m_image_y_size < 1) || (m_image_y_size > JPGD_MAX_HEIGHT)) stop_decoding(JPGD_BAD_HEIGHT);
if ((m_image_y_size < 1) || (m_image_y_size > JPGD_MAX_HEIGHT)) return stop_decoding(JPGD_BAD_HEIGHT);
m_image_x_size = get_bits(16);
if ((m_image_x_size < 1) || (m_image_x_size > JPGD_MAX_WIDTH)) stop_decoding(JPGD_BAD_WIDTH);
if ((m_image_x_size < 1) || (m_image_x_size > JPGD_MAX_WIDTH)) return stop_decoding(JPGD_BAD_WIDTH);
m_comps_in_frame = get_bits(8);
if (m_comps_in_frame > JPGD_MAX_COMPONENTS) stop_decoding(JPGD_TOO_MANY_COMPONENTS);
if (m_comps_in_frame > JPGD_MAX_COMPONENTS) return stop_decoding(JPGD_TOO_MANY_COMPONENTS);
if (num_left != (uint32_t)(m_comps_in_frame * 3 + 8)) stop_decoding(JPGD_BAD_SOF_LENGTH);
if (num_left != (uint32_t)(m_comps_in_frame * 3 + 8)) return stop_decoding(JPGD_BAD_SOF_LENGTH);
for (i = 0; i < m_comps_in_frame; i++) {
m_comp_ident[i] = get_bits(8);
@ -1265,33 +1257,36 @@ void jpeg_decoder::read_sof_marker()
m_comp_v_samp[i] = get_bits(4);
m_comp_quant[i] = get_bits(8);
}
return true;
}
// Used to skip unrecognized markers.
void jpeg_decoder::skip_variable_marker()
bool jpeg_decoder::skip_variable_marker()
{
uint32_t num_left = get_bits(16);
if (num_left < 2) stop_decoding(JPGD_BAD_VARIABLE_MARKER);
if (num_left < 2) return stop_decoding(JPGD_BAD_VARIABLE_MARKER);
num_left -= 2;
while (num_left) {
get_bits(8);
num_left--;
}
return true;
}
// Read a define restart interval (DRI) marker.
void jpeg_decoder::read_dri_marker()
bool jpeg_decoder::read_dri_marker()
{
if (get_bits(16) != 4) stop_decoding(JPGD_BAD_DRI_LENGTH);
if (get_bits(16) != 4) return stop_decoding(JPGD_BAD_DRI_LENGTH);
m_restart_interval = get_bits(16);
return true;
}
// Read a start of scan (SOS) marker.
void jpeg_decoder::read_sos_marker()
bool jpeg_decoder::read_sos_marker()
{
int i, ci, c, cc;
uint32_t num_left = get_bits(16);
@ -1300,7 +1295,7 @@ void jpeg_decoder::read_sos_marker()
m_comps_in_scan = n;
num_left -= 3;
if ( (num_left != (uint32_t)(n * 2 + 3)) || (n < 1) || (n > JPGD_MAX_COMPS_IN_SCAN) ) stop_decoding(JPGD_BAD_SOS_LENGTH);
if ( (num_left != (uint32_t)(n * 2 + 3)) || (n < 1) || (n > JPGD_MAX_COMPS_IN_SCAN) ) return stop_decoding(JPGD_BAD_SOS_LENGTH);
for (i = 0; i < n; i++) {
cc = get_bits(8);
@ -1310,7 +1305,7 @@ void jpeg_decoder::read_sos_marker()
for (ci = 0; ci < m_comps_in_frame; ci++)
if (cc == m_comp_ident[ci]) break;
if (ci >= m_comps_in_frame) stop_decoding(JPGD_BAD_SOS_COMP_ID);
if (ci >= m_comps_in_frame) return stop_decoding(JPGD_BAD_SOS_COMP_ID);
m_comp_list[i] = ci;
m_comp_dc_tab[ci] = (c >> 4) & 15;
@ -1331,6 +1326,7 @@ void jpeg_decoder::read_sos_marker()
get_bits(8);
num_left--;
}
return true;
}
@ -1380,21 +1376,21 @@ int jpeg_decoder::process_markers()
case M_EOI:
case M_SOS: return c;
case M_DHT: {
read_dht_marker();
break;
if (read_dht_marker()) break;
else return M_EOI;
}
// No arithmetic support - dumb patents!
case M_DAC: {
stop_decoding(JPGD_NO_ARITHMETIC_SUPPORT);
break;
return M_EOI;
}
case M_DQT: {
read_dqt_marker();
break;
if (read_dqt_marker()) break;
else return M_EOI;
}
case M_DRI: {
read_dri_marker();
break;
if (read_dri_marker()) break;
else return M_EOI;
}
//case M_APP0: /* no need to read the JFIF marker */
case M_JPG:
@ -1408,11 +1404,11 @@ int jpeg_decoder::process_markers()
case M_RST7:
case M_TEM: {
stop_decoding(JPGD_UNEXPECTED_MARKER);
break;
return M_EOI;
}
default: { /* must be DNL, DHP, EXP, APPn, JPGn, COM, or RESn or APP0 */
skip_variable_marker();
break;
if (skip_variable_marker()) break;
else return M_EOI;
}
}
}
@ -1422,71 +1418,62 @@ int jpeg_decoder::process_markers()
// Finds the start of image (SOI) marker.
// This code is rather defensive: it only checks the first 512 bytes to avoid
// false positives.
void jpeg_decoder::locate_soi_marker()
bool jpeg_decoder::locate_soi_marker()
{
uint32_t lastchar = get_bits(8);
uint32_t thischar = get_bits(8);
/* ok if it's a normal JPEG file without a special header */
if ((lastchar == 0xFF) && (thischar == M_SOI)) return;
if ((lastchar == 0xFF) && (thischar == M_SOI)) return true;
uint32_t bytesleft = 4096; //512;
while (true) {
if (--bytesleft == 0) stop_decoding(JPGD_NOT_JPEG);
if (--bytesleft == 0) return stop_decoding(JPGD_NOT_JPEG);
lastchar = thischar;
thischar = get_bits(8);
if (lastchar == 0xFF) {
if (thischar == M_SOI) break;
else if (thischar == M_EOI) stop_decoding(JPGD_NOT_JPEG); // get_bits will keep returning M_EOI if we read past the end
else if (thischar == M_EOI) return stop_decoding(JPGD_NOT_JPEG); // get_bits will keep returning M_EOI if we read past the end
}
}
// Check the next character after marker: if it's not 0xFF, it can't be the start of the next marker, so the file is bad.
thischar = (m_bit_buf >> 24) & 0xFF;
if (thischar != 0xFF) stop_decoding(JPGD_NOT_JPEG);
if (thischar != 0xFF) return stop_decoding(JPGD_NOT_JPEG);
return true;
}
// Find a start of frame (SOF) marker.
void jpeg_decoder::locate_sof_marker()
bool jpeg_decoder::locate_sof_marker()
{
locate_soi_marker();
int c = process_markers();
switch (c) {
if (!locate_soi_marker()) return false;
switch (process_markers()) {
case M_SOF2: {
m_progressive_flag = true;
read_sof_marker();
break;
return read_sof_marker();
}
case M_SOF0: /* baseline DCT */
case M_SOF1: { /* extended sequential DCT */
read_sof_marker();
break;
}
case M_SOF9: { /* Arithmetic coding */
stop_decoding(JPGD_NO_ARITHMETIC_SUPPORT);
break;
}
default: {
stop_decoding(JPGD_UNSUPPORTED_MARKER);
break;
return read_sof_marker();
}
case M_SOF9: return stop_decoding(JPGD_NO_ARITHMETIC_SUPPORT);
default: return stop_decoding(JPGD_UNSUPPORTED_MARKER);
}
return true;
}
// Find a start of scan (SOS) marker.
int jpeg_decoder::locate_sos_marker()
bool jpeg_decoder::locate_sos_marker()
{
int c = process_markers();
auto c = process_markers();
if (c == M_EOI) return false;
else if (c != M_SOS) stop_decoding(JPGD_UNEXPECTED_MARKER);
read_sos_marker();
return true;
else if (c != M_SOS) return stop_decoding(JPGD_UNEXPECTED_MARKER);
return read_sos_marker();
}
@ -1828,7 +1815,7 @@ void jpeg_decoder::load_next_row()
// Restart interval processing.
void jpeg_decoder::process_restart()
bool jpeg_decoder::process_restart()
{
int i;
int c = 0;
@ -1842,15 +1829,15 @@ void jpeg_decoder::process_restart()
for (i = 1536; i > 0; i--) {
if (get_char() == 0xFF) break;
}
if (i == 0) stop_decoding(JPGD_BAD_RESTART_MARKER);
if (i == 0) return stop_decoding(JPGD_BAD_RESTART_MARKER);
for ( ; i > 0; i--) {
if ((c = get_char()) != 0xFF) break;
}
if (i == 0) stop_decoding(JPGD_BAD_RESTART_MARKER);
if (i == 0) return stop_decoding(JPGD_BAD_RESTART_MARKER);
// Is it the expected marker? If not, something bad happened.
if (c != (m_next_restart_num + M_RST0)) stop_decoding(JPGD_BAD_RESTART_MARKER);
if (c != (m_next_restart_num + M_RST0)) return stop_decoding(JPGD_BAD_RESTART_MARKER);
// Reset each component's DC prediction values.
memset(&m_last_dc_val, 0, m_comps_in_frame * sizeof(uint32_t));
@ -1863,6 +1850,8 @@ void jpeg_decoder::process_restart()
m_bits_left = 16;
get_bits_no_markers(16);
get_bits_no_markers(16);
return true;
}
@ -1873,10 +1862,12 @@ static inline int dequantize_ac(int c, int q)
}
// Decodes and dequantizes the next row of coefficients.
void jpeg_decoder::decode_next_row()
bool jpeg_decoder::decode_next_row()
{
for (int mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) {
if ((m_restart_interval) && (m_restarts_left == 0)) process_restart();
if ((m_restart_interval) && (m_restarts_left == 0)) {
if (!process_restart()) return false;
}
jpgd_block_t* p = m_pMCU_coefficients;
@ -1903,7 +1894,7 @@ void jpeg_decoder::decode_next_row()
if (s) {
if (r) {
if ((k + r) > 63) stop_decoding(JPGD_DECODE_ERROR);
if ((k + r) > 63) return stop_decoding(JPGD_DECODE_ERROR);
if (k < prev_num_set) {
int n = JPGD_MIN(r, prev_num_set - k);
int kt = k;
@ -1916,7 +1907,7 @@ void jpeg_decoder::decode_next_row()
p[g_ZAG[k]] = static_cast<jpgd_block_t>(dequantize_ac(s, q[k])); //s * q[k];
} else {
if (r == 15) {
if ((k + 16) > 64) stop_decoding(JPGD_DECODE_ERROR);
if ((k + 16) > 64) return stop_decoding(JPGD_DECODE_ERROR);
if (k < prev_num_set) {
int n = JPGD_MIN(16, prev_num_set - k);
int kt = k;
@ -1942,6 +1933,7 @@ void jpeg_decoder::decode_next_row()
else transform_mcu(mcu_row);
m_restarts_left--;
}
return true;
}
@ -2183,9 +2175,8 @@ int jpeg_decoder::decode(const void** pScan_line, uint32_t* pScan_line_len)
if ((m_error_code) || (!m_ready_flag)) return JPGD_FAILED;
if (m_total_lines_left == 0) return JPGD_DONE;
if (m_mcu_lines_left == 0) {
if (setjmp(m_jmp_state)) return JPGD_FAILED;
if (m_progressive_flag) load_next_row();
else decode_next_row();
else if (!decode_next_row()) return JPGD_FAILED;
// Find the EOI marker if that was the last row.
if (m_total_lines_left <= m_max_mcu_y_size) find_eoi();
m_mcu_lines_left = m_max_mcu_y_size;
@ -2341,20 +2332,21 @@ void jpeg_decoder::make_huff_table(int index, huff_tables *pH)
// Verifies the quantization tables needed for this scan are available.
void jpeg_decoder::check_quant_tables()
bool jpeg_decoder::check_quant_tables()
{
for (int i = 0; i < m_comps_in_scan; i++) {
if (m_quant[m_comp_quant[m_comp_list[i]]] == nullptr) stop_decoding(JPGD_UNDEFINED_QUANT_TABLE);
if (!m_quant[m_comp_quant[m_comp_list[i]]]) return stop_decoding(JPGD_UNDEFINED_QUANT_TABLE);
}
return true;
}
// Verifies that all the Huffman tables needed for this scan are available.
void jpeg_decoder::check_huff_tables()
bool jpeg_decoder::check_huff_tables()
{
for (int i = 0; i < m_comps_in_scan; i++) {
if ((m_spectral_start == 0) && (m_huff_num[m_comp_dc_tab[m_comp_list[i]]] == nullptr)) stop_decoding(JPGD_UNDEFINED_HUFF_TABLE);
if ((m_spectral_end > 0) && (m_huff_num[m_comp_ac_tab[m_comp_list[i]]] == nullptr)) stop_decoding(JPGD_UNDEFINED_HUFF_TABLE);
if ((m_spectral_start == 0) && !m_huff_num[m_comp_dc_tab[m_comp_list[i]]]) return stop_decoding(JPGD_UNDEFINED_HUFF_TABLE);
if ((m_spectral_end > 0) && !m_huff_num[m_comp_ac_tab[m_comp_list[i]]]) return stop_decoding(JPGD_UNDEFINED_HUFF_TABLE);
}
for (int i = 0; i < JPGD_MAX_HUFF_TABLES; i++) {
@ -2363,6 +2355,7 @@ void jpeg_decoder::check_huff_tables()
make_huff_table(i, m_pHuff_tabs[i]);
}
}
return true;
}
@ -2418,7 +2411,7 @@ int jpeg_decoder::init_scan()
calc_mcu_block_order();
check_huff_tables();
check_quant_tables();
if (!check_quant_tables()) return false;
memset(m_last_dc_val, 0, m_comps_in_frame * sizeof(uint32_t));
@ -2435,19 +2428,20 @@ int jpeg_decoder::init_scan()
// Starts a frame. Determines if the number of components or sampling factors
// are supported.
void jpeg_decoder::init_frame()
bool jpeg_decoder::init_frame()
{
int i;
if (m_comps_in_frame == 1) {
if ((m_comp_h_samp[0] != 1) || (m_comp_v_samp[0] != 1)) stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
if ((m_comp_h_samp[0] != 1) || (m_comp_v_samp[0] != 1)) return stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
m_scan_type = JPGD_GRAYSCALE;
m_max_blocks_per_mcu = 1;
m_max_mcu_x_size = 8;
m_max_mcu_y_size = 8;
} else if (m_comps_in_frame == 3) {
if (((m_comp_h_samp[1] != 1) || (m_comp_v_samp[1] != 1)) || ((m_comp_h_samp[2] != 1) || (m_comp_v_samp[2] != 1)))
stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
if (((m_comp_h_samp[1] != 1) || (m_comp_v_samp[1] != 1)) || ((m_comp_h_samp[2] != 1) || (m_comp_v_samp[2] != 1))) {
return stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
}
if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) {
m_scan_type = JPGD_YH1V1;
@ -2469,8 +2463,8 @@ void jpeg_decoder::init_frame()
m_max_blocks_per_mcu = 6;
m_max_mcu_x_size = 16;
m_max_mcu_y_size = 16;
} else stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
} else stop_decoding(JPGD_UNSUPPORTED_COLORSPACE);
} else return stop_decoding(JPGD_UNSUPPORTED_SAMP_FACTORS);
} else return stop_decoding(JPGD_UNSUPPORTED_COLORSPACE);
m_max_mcus_per_row = (m_image_x_size + (m_max_mcu_x_size - 1)) / m_max_mcu_x_size;
m_max_mcus_per_col = (m_image_y_size + (m_max_mcu_y_size - 1)) / m_max_mcu_y_size;
@ -2491,7 +2485,7 @@ void jpeg_decoder::init_frame()
m_max_blocks_per_row = m_max_mcus_per_row * m_max_blocks_per_mcu;
// Should never happen
if (m_max_blocks_per_row > JPGD_MAX_BLOCKS_PER_ROW) stop_decoding(JPGD_ASSERTION_ERROR);
if (m_max_blocks_per_row > JPGD_MAX_BLOCKS_PER_ROW) return stop_decoding(JPGD_ASSERTION_ERROR);
// Allocate the coefficient buffer, enough for one MCU
m_pMCU_coefficients = (jpgd_block_t*)alloc(m_max_blocks_per_mcu * 64 * sizeof(jpgd_block_t));
@ -2517,6 +2511,8 @@ void jpeg_decoder::init_frame()
m_total_lines_left = m_image_y_size;
m_mcu_lines_left = 0;
create_look_ups();
return true;
}
@ -2546,7 +2542,7 @@ inline jpgd_block_t *jpeg_decoder::coeff_buf_getp(coeff_buf *cb, int block_x, in
// The following methods decode the various types of m_blocks encountered
// in progressively encoded images.
void jpeg_decoder::decode_block_dc_first(jpeg_decoder *pD, int component_id, int block_x, int block_y)
bool jpeg_decoder::decode_block_dc_first(jpeg_decoder *pD, int component_id, int block_x, int block_y)
{
int s, r;
jpgd_block_t *p = pD->coeff_buf_getp(pD->m_dc_coeffs[component_id], block_x, block_y);
@ -2557,25 +2553,28 @@ void jpeg_decoder::decode_block_dc_first(jpeg_decoder *pD, int component_id, int
}
pD->m_last_dc_val[component_id] = (s += pD->m_last_dc_val[component_id]);
p[0] = static_cast<jpgd_block_t>(static_cast<unsigned int>(s) << pD->m_successive_low);
return true;
}
void jpeg_decoder::decode_block_dc_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y)
bool jpeg_decoder::decode_block_dc_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y)
{
if (pD->get_bits_no_markers(1)) {
jpgd_block_t *p = pD->coeff_buf_getp(pD->m_dc_coeffs[component_id], block_x, block_y);
p[0] |= (1 << pD->m_successive_low);
}
return true;
}
void jpeg_decoder::decode_block_ac_first(jpeg_decoder *pD, int component_id, int block_x, int block_y)
bool jpeg_decoder::decode_block_ac_first(jpeg_decoder *pD, int component_id, int block_x, int block_y)
{
int k, s, r;
if (pD->m_eob_run) {
pD->m_eob_run--;
return;
return true;
}
jpgd_block_t *p = pD->coeff_buf_getp(pD->m_ac_coeffs[component_id], block_x, block_y);
@ -2584,13 +2583,13 @@ void jpeg_decoder::decode_block_ac_first(jpeg_decoder *pD, int component_id, int
r = s >> 4;
s &= 15;
if (s) {
if ((k += r) > 63) pD->stop_decoding(JPGD_DECODE_ERROR);
if ((k += r) > 63) return pD->stop_decoding(JPGD_DECODE_ERROR);
r = pD->get_bits_no_markers(s);
s = JPGD_HUFF_EXTEND(r, s);
p[g_ZAG[k]] = static_cast<jpgd_block_t>(static_cast<unsigned int>(s) << pD->m_successive_low);
} else {
if (r == 15) {
if ((k += 15) > 63) pD->stop_decoding(JPGD_DECODE_ERROR);
if ((k += 15) > 63) return pD->stop_decoding(JPGD_DECODE_ERROR);
} else {
pD->m_eob_run = 1 << r;
if (r) pD->m_eob_run += pD->get_bits_no_markers(r);
@ -2599,10 +2598,11 @@ void jpeg_decoder::decode_block_ac_first(jpeg_decoder *pD, int component_id, int
}
}
}
return true;
}
void jpeg_decoder::decode_block_ac_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y)
bool jpeg_decoder::decode_block_ac_refine(jpeg_decoder *pD, int component_id, int block_x, int block_y)
{
int s, k, r;
int p1 = 1 << pD->m_successive_low;
@ -2619,7 +2619,7 @@ void jpeg_decoder::decode_block_ac_refine(jpeg_decoder *pD, int component_id, in
r = s >> 4;
s &= 15;
if (s) {
if (s != 1) pD->stop_decoding(JPGD_DECODE_ERROR);
if (s != 1) return pD->stop_decoding(JPGD_DECODE_ERROR);
if (pD->get_bits_no_markers(1)) s = p1;
else s = m1;
} else {
@ -2667,11 +2667,12 @@ void jpeg_decoder::decode_block_ac_refine(jpeg_decoder *pD, int component_id, in
}
pD->m_eob_run--;
}
return true;
}
// Decode a scan in a progressively encoded image.
void jpeg_decoder::decode_scan(pDecode_block_func decode_block_func)
bool jpeg_decoder::decode_scan(pDecode_block_func decode_block_func)
{
int mcu_row, mcu_col, mcu_block;
int block_x_mcu[JPGD_MAX_COMPONENTS], m_block_y_mcu[JPGD_MAX_COMPONENTS];
@ -2685,11 +2686,13 @@ void jpeg_decoder::decode_scan(pDecode_block_func decode_block_func)
for (mcu_row = 0; mcu_row < m_mcus_per_row; mcu_row++) {
int block_x_mcu_ofs = 0, block_y_mcu_ofs = 0;
if ((m_restart_interval) && (m_restarts_left == 0)) process_restart();
if ((m_restart_interval) && (m_restarts_left == 0)) {
if (!process_restart()) return false;
}
for (mcu_block = 0; mcu_block < m_blocks_per_mcu; mcu_block++) {
component_id = m_mcu_org[mcu_block];
decode_block_func(this, component_id, block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs);
if (!decode_block_func(this, component_id, block_x_mcu[component_id] + block_x_mcu_ofs, m_block_y_mcu[component_id] + block_y_mcu_ofs)) return false;
if (m_comps_in_scan == 1) block_x_mcu[component_id]++;
else {
@ -2714,15 +2717,16 @@ void jpeg_decoder::decode_scan(pDecode_block_func decode_block_func)
}
}
}
return true;
}
// Decode a progressively encoded image.
void jpeg_decoder::init_progressive()
bool jpeg_decoder::init_progressive()
{
int i;
if (m_comps_in_frame == 4) stop_decoding(JPGD_UNSUPPORTED_COLORSPACE);
if (m_comps_in_frame == 4) return stop_decoding(JPGD_UNSUPPORTED_COLORSPACE);
// Allocate the coefficient buffers.
for (i = 0; i < m_comps_in_frame; i++) {
@ -2739,15 +2743,13 @@ void jpeg_decoder::init_progressive()
dc_only_scan = (m_spectral_start == 0);
refinement_scan = (m_successive_high != 0);
if ((m_spectral_start > m_spectral_end) || (m_spectral_end > 63)) stop_decoding(JPGD_BAD_SOS_SPECTRAL);
if ((m_spectral_start > m_spectral_end) || (m_spectral_end > 63)) return stop_decoding(JPGD_BAD_SOS_SPECTRAL);
if (dc_only_scan) {
if (m_spectral_end) stop_decoding(JPGD_BAD_SOS_SPECTRAL);
} else if (m_comps_in_scan != 1) { /* AC scans can only contain one component */
stop_decoding(JPGD_BAD_SOS_SPECTRAL);
}
if (m_spectral_end) return stop_decoding(JPGD_BAD_SOS_SPECTRAL);
} else if (m_comps_in_scan != 1) return stop_decoding(JPGD_BAD_SOS_SPECTRAL); // AC scans can only contain one component
if ((refinement_scan) && (m_successive_low != m_successive_high - 1)) stop_decoding(JPGD_BAD_SOS_SUCCESSIVE);
if ((refinement_scan) && (m_successive_low != m_successive_high - 1)) return stop_decoding(JPGD_BAD_SOS_SUCCESSIVE);
if (dc_only_scan) {
if (refinement_scan) decode_block_func = decode_block_dc_refine;
@ -2756,7 +2758,7 @@ void jpeg_decoder::init_progressive()
if (refinement_scan) decode_block_func = decode_block_ac_refine;
else decode_block_func = decode_block_ac_first;
}
decode_scan(decode_block_func);
if (!decode_scan(decode_block_func)) return false;
m_bits_left = 16;
get_bits(16);
get_bits(16);
@ -2769,20 +2771,23 @@ void jpeg_decoder::init_progressive()
}
calc_mcu_block_order();
return true;
}
void jpeg_decoder::init_sequential()
bool jpeg_decoder::init_sequential()
{
if (!init_scan()) stop_decoding(JPGD_UNEXPECTED_MARKER);
if (!init_scan()) return stop_decoding(JPGD_UNEXPECTED_MARKER);
return true;
}
void jpeg_decoder::decode_start()
bool jpeg_decoder::decode_start()
{
init_frame();
if (m_progressive_flag) init_progressive();
else init_sequential();
if (!init_frame()) return false;
if (m_progressive_flag) return init_progressive();
return init_sequential();
}
@ -2795,7 +2800,6 @@ void jpeg_decoder::decode_init(jpeg_decoder_stream *pStream)
jpeg_decoder::jpeg_decoder(jpeg_decoder_stream *pStream)
{
if (setjmp(m_jmp_state)) return;
decode_init(pStream);
}
@ -2804,9 +2808,7 @@ int jpeg_decoder::begin_decoding()
{
if (m_ready_flag) return JPGD_SUCCESS;
if (m_error_code) return JPGD_FAILED;
if (setjmp(m_jmp_state)) return JPGD_FAILED;
decode_start();
if (!decode_start()) return JPGD_FAILED;
m_ready_flag = true;
return JPGD_SUCCESS;
@ -2970,7 +2972,7 @@ unsigned char* jpgdDecompress(jpeg_decoder* decoder)
if (!pImage_data) return nullptr;
for (int y = 0; y < image_height; y++) {
const uint8_t* pScan_line;
const uint8_t* pScan_line = nullptr;
uint32_t scan_line_len;
if (decoder->decode((const void**)&pScan_line, &scan_line_len) != JPGD_SUCCESS) {
tvg::free(pImage_data);

View file

@ -352,13 +352,11 @@ static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx)
for (int i = 0; i < repeater->cnt; ++i) {
auto multiplier = repeater->offset + static_cast<float>(i);
ARRAY_FOREACH(p, propagators) {
auto shape = static_cast<Shape*>((*p)->duplicate());
SHAPE(shape)->rs.path = SHAPE(path)->rs.path;
auto opacity = repeater->interpOpacity ? tvg::lerp<uint8_t>(repeater->startOpacity, repeater->endOpacity, static_cast<float>(i + 1) / repeater->cnt) : repeater->startOpacity;
shape->opacity(opacity);
auto opacity = tvg::lerp<uint8_t>(repeater->startOpacity, repeater->endOpacity, static_cast<float>(i + 1) / repeater->cnt);
shape->opacity(MULTIPLY((*p)->opacity(), opacity));
Matrix m;
tvg::identity(&m);
@ -366,11 +364,10 @@ static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx)
scale(&m, {powf(repeater->scale.x * 0.01f, multiplier), powf(repeater->scale.y * 0.01f, multiplier)});
rotate(&m, repeater->rotation * multiplier);
translateR(&m, -repeater->anchor);
m = repeater->transform * m;
Matrix inv;
inverse(&repeater->transform, &inv);
shape->transform(m * (inv * shape->transform()));
shape->transform((repeater->transform * m) * (inv * shape->transform()));
shapes.push(shape);
}
}
@ -613,7 +610,7 @@ void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, flo
auto radius = star->outerRadius(frameNo, tween, exps);
auto outerRoundness = star->outerRoundness(frameNo, tween, exps) * 0.01f;
auto angle = deg2rad(-90.0f);
auto angle = -MATH_PI2;
auto anglePerPoint = 2.0f * MATH_PI / float(ptsCnt);
auto direction = star->clockwise ? 1.0f : -1.0f;
auto hasRoundness = !tvg::zero(outerRoundness);
@ -641,6 +638,7 @@ void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, flo
auto in = Point{x, y} * transform;
shape->moveTo(in.x, in.y);
auto coeff = anglePerPoint * radius * outerRoundness * POLYGON_MAGIC_NUMBER;
for (size_t i = 0; i < ptsCnt; i++) {
auto previousX = x;
auto previousY = y;
@ -649,16 +647,11 @@ void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, flo
if (hasRoundness) {
auto cp1Theta = tvg::atan2(previousY, previousX) - MATH_PI2 * direction;
auto cp1Dx = cosf(cp1Theta);
auto cp1Dy = sinf(cp1Theta);
auto cp1x = coeff * cosf(cp1Theta);
auto cp1y = coeff * sinf(cp1Theta);
auto cp2Theta = tvg::atan2(y, x) - MATH_PI2 * direction;
auto cp2Dx = cosf(cp2Theta);
auto cp2Dy = sinf(cp2Theta);
auto cp1x = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp1Dx;
auto cp1y = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp1Dy;
auto cp2x = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dx;
auto cp2y = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dy;
auto cp2x = coeff * cosf(cp2Theta);
auto cp2y = coeff * sinf(cp2Theta);
auto in2 = Point{previousX - cp1x, previousY - cp1y} * transform;
auto in3 = Point{x + cp2x, y + cp2y} * transform;
@ -743,7 +736,6 @@ void LottieBuilder::updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject*
r.startOpacity = repeater->startOpacity(frameNo, tween, exps);
r.endOpacity = repeater->endOpacity(frameNo, tween, exps);
r.inorder = repeater->inorder;
r.interpOpacity = (r.startOpacity == r.endOpacity) ? false : true;
ctx->repeaters.push(r);
ctx->merging = nullptr;
@ -938,7 +930,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
if (!p || !text->font) return;
if (text->font->origin != LottieFont::Origin::Embedded) {
if (text->font->origin != LottieFont::Origin::Local || text->font->chars.empty()) {
_fontText(doc, layer->scene);
return;
}
@ -1178,8 +1170,8 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo)
{
if (layer->masks.count == 0) return;
//Introduce an intermediate scene for embracing the matte + masking
if (layer->matteTarget) {
//Introduce an intermediate scene for embracing matte + masking or precomp clipping + masking replaced by clipping
if (layer->matteTarget || layer->type == LottieLayer::Precomp) {
auto scene = Scene::gen();
scene->push(layer->scene);
layer->scene = scene;
@ -1196,7 +1188,6 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo)
auto method = mask->method;
auto opacity = mask->opacity(frameNo);
auto expand = mask->expand(frameNo);
auto fastTrack = false; //single clipping
//the first mask
if (!pShape) {
@ -1207,7 +1198,6 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo)
if (layer->masks.count == 1 && compMethod == MaskMethod::Alpha) {
layer->scene->opacity(MULTIPLY(layer->scene->opacity(), opacity));
layer->scene->clip(pShape);
fastTrack = true;
} else {
layer->scene->mask(pShape, compMethod);
}
@ -1233,9 +1223,6 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo)
auto offset = LottieOffsetModifier(expand);
mask->pathset(frameNo, SHAPE(pShape)->rs.path, nullptr, tween, exps, &offset);
}
if (fastTrack) return;
pOpacity = opacity;
pMethod = method;
}
@ -1245,7 +1232,7 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo)
bool LottieBuilder::updateMatte(LottieComposition* comp, float frameNo, Scene* scene, LottieLayer* layer)
{
auto target = layer->matteTarget;
if (!target) return true;
if (!target || target->type == LottieLayer::Null) return true;
updateLayer(comp, scene, target, frameNo);

View file

@ -42,7 +42,6 @@ struct RenderRepeater
float rotation;
uint8_t startOpacity;
uint8_t endOpacity;
bool interpOpacity;
bool inorder;
};

View file

@ -80,36 +80,47 @@ float LottieTextFollowPath::prepare(LottieMask* mask, float frameNo, float scale
Point LottieTextFollowPath::position(float lenSearched, float& angle)
{
//position before the start of the curve
if (lenSearched <= 0.0f) {
//shape is closed -> wrapping
if (path.cmds.last() == PathCommand::Close) {
while (lenSearched < 0.0f) lenSearched += totalLen;
pts = path.pts.data;
cmds = path.cmds.data;
cmdsCnt = path.cmds.count;
currentLen = 0.0f;
//linear interpolation
} else {
if (cmds >= path.cmds.data + path.cmds.count - 1) return *start;
switch (*(cmds + 1)) {
case PathCommand::LineTo: {
auto dp = *(pts + 1) - *pts;
angle = tvg::atan2(dp.y, dp.x);
return {pts->x + lenSearched * cos(angle), pts->y + lenSearched * sin(angle)};
}
case PathCommand::CubicTo: {
angle = deg2rad(Bezier{*pts, *(pts + 1), *(pts + 2), *(pts + 3)}.angle(0.0001f));
return {pts->x + lenSearched * cos(angle), pts->y + lenSearched * sin(angle)};
}
default:
angle = 0.0f;
return *start;
}
}
}
auto shift = [&]() -> void {
switch (*cmds) {
case PathCommand::MoveTo:
start = pts;
++pts;
break;
case PathCommand::LineTo:
++pts;
break;
case PathCommand::CubicTo:
pts += 3;
break;
case PathCommand::Close:
break;
case PathCommand::MoveTo: start = pts; ++pts; break;
case PathCommand::LineTo: ++pts; break;
case PathCommand::CubicTo: pts += 3; break;
case PathCommand::Close: break;
}
++cmds;
--cmdsCnt;
};
auto length = [&]() -> float {
switch (*cmds) {
case PathCommand::MoveTo: return 0.0f;
case PathCommand::LineTo: return tvg::length(pts - 1, pts);
case PathCommand::CubicTo: return Bezier{*(pts - 1), *pts, *(pts + 1), *(pts + 2)}.length();
case PathCommand::Close: return tvg::length(pts - 1, start);
}
return 0.0f;
};
//beyond the curve
//position beyond the end of the curve
if (lenSearched >= totalLen) {
//shape is closed -> wrapping
if (path.cmds.last() == PathCommand::Close) {
@ -146,6 +157,24 @@ Point LottieTextFollowPath::position(float lenSearched, float& angle)
}
}
//reset required if text partially crosses curve start
if (lenSearched < currentLen) {
pts = path.pts.data;
cmds = path.cmds.data;
cmdsCnt = path.cmds.count;
currentLen = 0.0f;
}
auto length = [&]() -> float {
switch (*cmds) {
case PathCommand::MoveTo: return 0.0f;
case PathCommand::LineTo: return tvg::length(pts - 1, pts);
case PathCommand::CubicTo: return Bezier{*(pts - 1), *pts, *(pts + 1), *(pts + 2)}.length();
case PathCommand::Close: return tvg::length(pts - 1, start);
default: return 0.0f;
}
};
while (cmdsCnt > 0) {
auto dLen = length();
if (currentLen + dLen < lenSearched) {
@ -664,7 +693,7 @@ void LottieLayer::prepare(RGB24* color)
float LottieLayer::remap(LottieComposition* comp, float frameNo, LottieExpressions* exp)
{
if (timeRemap.frames || timeRemap.value) {
if (timeRemap.frames || timeRemap.value >= 0.0f) {
frameNo = comp->frameAtTime(timeRemap(frameNo, exp));
} else {
frameNo -= startFrame;

View file

@ -386,7 +386,7 @@ struct LottieTextRange
struct LottieFont
{
enum Origin : uint8_t { Local = 0, CssURL, ScriptURL, FontURL, Embedded };
enum Origin : uint8_t {Local = 0, CssURL, ScriptURL, FontURL};
~LottieFont()
{
@ -408,7 +408,7 @@ struct LottieFont
char* style = nullptr;
size_t dataSize = 0;
float ascent = 0.0f;
Origin origin = Embedded;
Origin origin = Local;
void prepare();
};
@ -980,7 +980,7 @@ struct LottieLayer : LottieGroup
char* name = nullptr;
LottieLayer* parent = nullptr;
LottieFloat timeRemap = 0.0f;
LottieFloat timeRemap = -1.0f;
LottieLayer* comp = nullptr; //Precompositor, current layer is belonges.
LottieTransform* transform = nullptr;
Array<LottieMask*> masks;

View file

@ -27,6 +27,12 @@
/* Internal Class Implementation */
/************************************************************************/
static bool _colinear(const Point* p)
{
return tvg::zero(*p - *(p + 1)) && tvg::zero(*(p + 2) - *(p + 3));
}
static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, Point& prev, Point& curr, Point& next, float r)
{
auto lenPrev = length(prev - curr);
@ -115,9 +121,12 @@ void LottieOffsetModifier::corner(RenderPath& out, Line& line, Line& nextLine, u
auto norm = normal(line.pt1, line.pt2);
auto nextNorm = normal(nextLine.pt1, nextLine.pt2);
auto miterDirection = (norm + nextNorm) / length(norm + nextNorm);
if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) {
out.cmds.push(PathCommand::LineTo);
if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) out.pts.push(intersect);
else out.pts.push(nextLine.pt1);
out.pts.push(intersect);
}
out.cmds.push(PathCommand::LineTo);
out.pts.push(nextLine.pt1);
} else {
out.cmds.push(PathCommand::LineTo);
out.pts.push(nextLine.pt1);
@ -192,14 +201,10 @@ bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt
break;
}
case PathCommand::CubicTo: {
if (iCmds < inCmdsCnt - 1 && _colinear(inPts + iPts - 1)) {
auto& prev = inPts[iPts - 1];
auto& curr = inPts[iPts + 2];
if (iCmds < inCmdsCnt - 1 &&
tvg::zero(inPts[iPts - 1] - inPts[iPts]) &&
tvg::zero(inPts[iPts + 1] - inPts[iPts + 2])) {
if (inCmds[iCmds + 1] == PathCommand::CubicTo &&
tvg::zero(inPts[iPts + 2] - inPts[iPts + 3]) &&
tvg::zero(inPts[iPts + 4] - inPts[iPts + 5])) {
if (inCmds[iCmds + 1] == PathCommand::CubicTo && _colinear(inPts + iPts + 2)) {
_roundCorner(path.cmds, path.pts, prev, curr, inPts[iPts + 5], r);
iPts += 3;
break;

View file

@ -3401,6 +3401,13 @@ static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content,
}
}
for (unsigned int i = 0; i < sizeof(gradientTags) / sizeof(gradientTags[0]); i++) {
if (!strncmp(tagName, gradientTags[i].tag, sz)) {
loader->gradientStack.pop();
break;
}
}
for (unsigned int i = 0; i < sizeof(graphicsTags) / sizeof(graphicsTags[0]); i++) {
if (!strncmp(tagName, graphicsTags[i].tag, sz)) {
loader->currentGraphicsNode = nullptr;
@ -3472,7 +3479,6 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
if (node->type != SvgNodeType::Defs || !empty) {
loader->stack.push(node);
}
loader->latestGradient = nullptr;
} else if ((method = _findGraphicsFactory(tagName))) {
if (loader->stack.count > 0) parent = loader->stack.last();
else parent = loader->doc;
@ -3483,10 +3489,11 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
loader->stack.push(defs);
loader->currentGraphicsNode = node;
}
loader->latestGradient = nullptr;
} else if ((gradientMethod = _findGradientFactory(tagName))) {
SvgStyleGradient* gradient;
gradient = gradientMethod(loader, attrs, attrsLength);
//Gradients do not allow nested declarations, so only the earliest declared Gradient is valid.
if (loader->gradientStack.count == 0) {
//FIXME: The current parsing structure does not distinguish end tags.
// There is no way to know if the currently parsed gradient is in defs.
// If a gradient is declared outside of defs after defs is set, it is included in the gradients of defs.
@ -3498,9 +3505,10 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
} else {
loader->gradients.push(gradient);
}
loader->latestGradient = gradient;
}
if (!empty) loader->gradientStack.push(gradient);
} else if (STR_AS(tagName, "stop")) {
if (!loader->latestGradient) {
if (loader->gradientStack.count == 0) {
TVGLOG("SVG", "Stop element is used outside of the Gradient element");
return;
}
@ -3508,9 +3516,8 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
loader->svgParse->gradStop = {0.0f, 0, 0, 0, 255};
loader->svgParse->flags = SvgStopStyleFlags::StopDefault;
xmlParseAttributes(attrs, attrsLength, _attrParseStops, loader);
loader->latestGradient->stops.push(loader->svgParse->gradStop);
loader->gradientStack.last()->stops.push(loader->svgParse->gradStop);
} else {
loader->latestGradient = nullptr;
if (!isIgnoreUnsupportedLogElements(tagName)) TVGLOG("SVG", "Unsupported elements used [Elements: %s]", tagName);
}
}
@ -3831,6 +3838,7 @@ void SvgLoader::clear(bool all)
tvg::free(*p);
}
loaderData.gradients.reset();
loaderData.gradientStack.reset();
_freeNode(loaderData.doc);
loaderData.doc = nullptr;

View file

@ -590,7 +590,7 @@ struct SvgLoaderData
SvgNode* def = nullptr; //also used to store nested graphic nodes
SvgNode* cssStyle = nullptr;
Array<SvgStyleGradient*> gradients;
SvgStyleGradient* latestGradient = nullptr; //For stops
Array<SvgStyleGradient*> gradientStack; //For stops
SvgParser* svgParse = nullptr;
Array<SvgNodeIdPair> cloneNodes;
Array<SvgNodeIdPair> nodesToStyle;

View file

@ -57,16 +57,17 @@ bool GlGeometry::tesselate(const RenderShape& rshape, RenderUpdateFlag flag)
bool GlGeometry::tesselate(const RenderSurface* image, RenderUpdateFlag flag)
{
if (flag & RenderUpdateFlag::Image) {
if (!(flag & RenderUpdateFlag::Image)) return true;
fill.clear();
fill.vertex.reserve(5 * 4);
fill.index.reserve(6);
float left = 0.f;
float top = 0.f;
float right = image->w;
float bottom = image->h;
auto left = 0.f;
auto top = 0.f;
auto right = float(image->w);
auto bottom = float(image->h);
// left top point
fill.vertex.push(left);
@ -101,11 +102,7 @@ bool GlGeometry::tesselate(const RenderSurface* image, RenderUpdateFlag flag)
fill.index.push(1);
fill.index.push(3);
bounds.x = 0;
bounds.y = 0;
bounds.w = image->w;
bounds.h = image->h;
}
bounds = {{0, 0}, {int32_t(image->w), int32_t(image->h)}};
return true;
}
@ -155,31 +152,20 @@ GlStencilMode GlGeometry::getStencilMode(RenderUpdateFlag flag)
RenderRegion GlGeometry::getBounds() const
{
if (tvg::identity(&matrix)) {
return bounds;
} else {
Point lt{static_cast<float>(bounds.x), static_cast<float>(bounds.y)};
Point lb{static_cast<float>(bounds.x), static_cast<float>(bounds.y + bounds.h)};
Point rt{static_cast<float>(bounds.x + bounds.w), static_cast<float>(bounds.y)};
Point rb{static_cast<float>(bounds.x + bounds.w), static_cast<float>(bounds.y + bounds.h)};
if (tvg::identity(&matrix)) return bounds;
lt *= matrix;
lb *= matrix;
rt *= matrix;
rb *= matrix;
auto lt = Point{float(bounds.min.x), float(bounds.min.y)} * matrix;
auto lb = Point{float(bounds.min.x), float(bounds.max.y)} * matrix;
auto rt = Point{float(bounds.max.x), float(bounds.min.y)} * matrix;
auto rb = Point{float(bounds.max.x), float(bounds.max.y)} * matrix;
float left = min(min(lt.x, lb.x), min(rt.x, rb.x));
float top = min(min(lt.y, lb.y), min(rt.y, rb.y));
float right = max(max(lt.x, lb.x), max(rt.x, rb.x));
float bottom = max(max(lt.y, lb.y), max(rt.y, rb.y));
auto left = min(min(lt.x, lb.x), min(rt.x, rb.x));
auto top = min(min(lt.y, lb.y), min(rt.y, rb.y));
auto right = max(max(lt.x, lb.x), max(rt.x, rb.x));
auto bottom = max(max(lt.y, lb.y), max(rt.y, rb.y));
auto bounds = RenderRegion {{int32_t(floor(left)), int32_t(floor(top))}, {int32_t(ceil(right)), int32_t(ceil(bottom))}};
if (bounds.valid()) return bounds;
return this->bounds;
auto bounds = RenderRegion {
static_cast<int32_t>(floor(left)),
static_cast<int32_t>(floor(top)),
static_cast<int32_t>(ceil(right - floor(left))),
static_cast<int32_t>(ceil(bottom - floor(top))),
};
if (bounds.w < 0 || bounds.h < 0) return this->bounds;
else return bounds;
}
}

View file

@ -52,17 +52,17 @@ void GlRenderPass::addRenderTask(GlRenderTask* task)
void GlRenderPass::getMatrix(float *dst, const Matrix &matrix) const
{
const auto& vp = getViewport();
Matrix postMatrix{};
tvg::identity(&postMatrix);
translate(&postMatrix, {(float)-vp.x, (float)-vp.y});
const auto& vp = getViewport();
translate(&postMatrix, {(float)-vp.sx(), (float)-vp.sy()});
auto m = postMatrix * matrix;
float modelMatrix[16];
GET_MATRIX44(m, modelMatrix);
MVP_MATRIX(vp.w, vp.h);
MVP_MATRIX(vp.w(), vp.h());
MULTIPLY_MATRIX(mvp, modelMatrix, dst);
}

View file

@ -62,10 +62,7 @@ public:
}
auto task = new T(program, targetFbo, mFbo, std::move(mTasks));
const auto& vp = mFbo->getViewport();
task->setRenderSize(static_cast<uint32_t>(vp.w), static_cast<uint32_t>(vp.h));
task->setRenderSize(mFbo->getViewport().w(), mFbo->getViewport().h());
return task;
}

View file

@ -22,26 +22,20 @@
#include "tvgGlRenderTarget.h"
GlRenderTarget::GlRenderTarget(uint32_t width, uint32_t height): mWidth(width), mHeight(height) {}
GlRenderTarget::GlRenderTarget() {}
GlRenderTarget::~GlRenderTarget()
{
if (mFbo == 0) return;
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
GL_CHECK(glDeleteFramebuffers(1, &mFbo));
if (mColorTex != 0) {
GL_CHECK(glDeleteTextures(1, &mColorTex));
}
if (mDepthStencilBuffer != 0) {
GL_CHECK(glDeleteRenderbuffers(1, &mDepthStencilBuffer));
}
reset();
}
void GlRenderTarget::init(GLint resolveId)
void GlRenderTarget::init(uint32_t width, uint32_t height, GLint resolveId)
{
if (mFbo != 0 || mWidth == 0 || mHeight == 0) return;
if (mFbo != GL_INVALID_VALUE || width == 0 || height == 0) return;
mWidth = width;
mHeight = height;
//TODO: fbo is used. maybe we can consider the direct rendering with resolveId as well.
GL_CHECK(glGenFramebuffers(1, &mFbo));
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mFbo));
@ -81,6 +75,18 @@ void GlRenderTarget::init(GLint resolveId)
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, resolveId));
}
void GlRenderTarget::reset()
{
if (mFbo == 0) return;
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
GL_CHECK(glDeleteFramebuffers(1, &mFbo));
GL_CHECK(glDeleteRenderbuffers(1, &mColorBuffer));
GL_CHECK(glDeleteRenderbuffers(1, &mDepthStencilBuffer));
GL_CHECK(glDeleteFramebuffers(1, &mResolveFbo));
GL_CHECK(glDeleteTextures(1, &mColorTex));
mFbo = GL_INVALID_VALUE;
}
GlRenderTargetPool::GlRenderTargetPool(uint32_t maxWidth, uint32_t maxHeight): mMaxWidth(maxWidth), mMaxHeight(maxHeight), mPool() {}
GlRenderTargetPool::~GlRenderTargetPool()
@ -101,31 +107,28 @@ uint32_t alignPow2(uint32_t value)
GlRenderTarget* GlRenderTargetPool::getRenderTarget(const RenderRegion& vp, GLuint resolveId)
{
uint32_t width = static_cast<uint32_t>(vp.w);
uint32_t height = static_cast<uint32_t>(vp.h);
auto width = vp.w();
auto height = vp.h();
// pow2 align width and height
if (width >= mMaxWidth) width = mMaxWidth;
else width = alignPow2(width);
if (width >= mMaxWidth) width = mMaxWidth;
if (height >= mMaxHeight) height = mMaxHeight;
else height = alignPow2(height);
if (height >= mMaxHeight) height = mMaxHeight;
for (uint32_t i = 0; i < mPool.count; i++) {
auto rt = mPool[i];
if (rt->getWidth() == width && rt->getHeight() == height) {
rt->setViewport(vp);
return rt;
}
}
auto rt = new GlRenderTarget(width, height);
rt->init(resolveId);
auto rt = new GlRenderTarget();
rt->init(width, height, resolveId);
rt->setViewport(vp);
mPool.push(rt);
return rt;

View file

@ -28,11 +28,11 @@
class GlRenderTarget
{
public:
GlRenderTarget() = default;
GlRenderTarget(uint32_t width, uint32_t height);
GlRenderTarget();
~GlRenderTarget();
void init(GLint resolveId);
void init(uint32_t width, uint32_t height, GLint resolveId);
void reset();
GLuint getFboId() { return mFbo; }
GLuint getResolveFboId() { return mResolveFbo; }
@ -44,12 +44,13 @@ public:
void setViewport(const RenderRegion& vp) { mViewport = vp; }
const RenderRegion& getViewport() const { return mViewport; }
bool invalid() const { return mFbo == GL_INVALID_VALUE; }
private:
uint32_t mWidth = 0;
uint32_t mHeight = 0;
RenderRegion mViewport{};
GLuint mFbo = 0;
GLuint mFbo = GL_INVALID_VALUE;
GLuint mColorBuffer = 0;
GLuint mDepthStencilBuffer = 0;
GLuint mResolveFbo = 0;

View file

@ -24,6 +24,9 @@
#include "tvgGlProgram.h"
#include "tvgGlRenderPass.h"
/************************************************************************/
/* GlRenderTask Class Implementation */
/************************************************************************/
GlRenderTask::GlRenderTask(GlProgram* program, GlRenderTask* other): mProgram(program)
{
@ -33,6 +36,7 @@ GlRenderTask::GlRenderTask(GlProgram* program, GlRenderTask* other): mProgram(pr
mIndexCount = other->mIndexCount;
}
void GlRenderTask::run()
{
// bind shader
@ -45,7 +49,7 @@ void GlRenderTask::run()
}
// setup scissor rect
GL_CHECK(glScissor(mViewport.x, mViewport.y, mViewport.w, mViewport.h));
GL_CHECK(glScissor(mViewport.sx(), mViewport.sy(), mViewport.sw(), mViewport.sh()));
// setup attribute layout
for (uint32_t i = 0; i < mVertexLayout.count; i++) {
@ -81,36 +85,44 @@ void GlRenderTask::run()
}
}
void GlRenderTask::addVertexLayout(const GlVertexLayout &layout)
{
mVertexLayout.push(layout);
}
void GlRenderTask::addBindResource(const GlBindingResource &binding)
{
mBindingResources.push(binding);
}
void GlRenderTask::setDrawRange(uint32_t offset, uint32_t count)
{
mIndexOffset = offset;
mIndexCount = count;
}
void GlRenderTask::setViewport(const RenderRegion &viewport)
{
mViewport = viewport;
if (mViewport.w < 0) {
mViewport.w = 0;
}
if (mViewport.h < 0) {
mViewport.h = 0;
}
if (mViewport.max.x < mViewport.min.x) mViewport.max.x = mViewport.min.x;
if (mViewport.max.y < mViewport.min.y) mViewport.max.y = mViewport.min.y;
}
/************************************************************************/
/* GlStencilCoverTask Class Implementation */
/************************************************************************/
GlStencilCoverTask::GlStencilCoverTask(GlRenderTask* stencil, GlRenderTask* cover, GlStencilMode mode)
:GlRenderTask(nullptr), mStencilTask(stencil), mCoverTask(cover), mStencilMode(mode) {}
:GlRenderTask(nullptr), mStencilTask(stencil), mCoverTask(cover), mStencilMode(mode)
{
}
GlStencilCoverTask::~GlStencilCoverTask()
{
@ -118,6 +130,7 @@ GlStencilCoverTask::~GlStencilCoverTask()
delete mCoverTask;
}
void GlStencilCoverTask::run()
{
GL_CHECK(glEnable(GL_STENCIL_TEST));
@ -151,12 +164,18 @@ void GlStencilCoverTask::run()
GL_CHECK(glDisable(GL_STENCIL_TEST));
}
void GlStencilCoverTask::normalizeDrawDepth(int32_t maxDepth)
{
mCoverTask->normalizeDrawDepth(maxDepth);
mStencilTask->normalizeDrawDepth(maxDepth);
}
/************************************************************************/
/* GlComposeTask Class Implementation */
/************************************************************************/
GlComposeTask::GlComposeTask(GlProgram* program, GLuint target, GlRenderTarget* fbo, Array<GlRenderTask*>&& tasks)
:GlRenderTask(program) ,mTargetFbo(target), mFbo(fbo), mTasks()
{
@ -164,33 +183,32 @@ GlComposeTask::GlComposeTask(GlProgram* program, GLuint target, GlRenderTarget*
tasks.clear();
}
GlComposeTask::~GlComposeTask()
{
ARRAY_FOREACH(p, mTasks) delete(*p);
mTasks.clear();
}
void GlComposeTask::run()
{
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, getSelfFbo()));
// clear this fbo
if (mClearBuffer) {
// we must clear all area of fbo
GL_CHECK(glViewport(0, 0, mFbo->getWidth(), mFbo->getHeight()));
GL_CHECK(glScissor(0, 0, mFbo->getWidth(), mFbo->getHeight()));
GL_CHECK(glClearColor(0, 0, 0, 0));
GL_CHECK(glClearStencil(0));
#ifdef THORVG_GL_TARGET_GLES
#ifdef THORVG_GL_TARGET_GLES
GL_CHECK(glClearDepthf(0.0));
#else
#else
GL_CHECK(glClearDepth(0.0));
#endif
#endif
GL_CHECK(glDepthMask(1));
GL_CHECK(glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
GL_CHECK(glDepthMask(0));
}
GL_CHECK(glViewport(0, 0, mRenderWidth, mRenderHeight));
GL_CHECK(glScissor(0, 0, mRenderWidth, mRenderHeight));
@ -209,29 +227,43 @@ void GlComposeTask::run()
onResolve();
}
GLuint GlComposeTask::getSelfFbo() { return mFbo->getFboId(); }
GLuint GlComposeTask::getResolveFboId() { return mFbo->getResolveFboId(); }
GLuint GlComposeTask::getSelfFbo()
{
return mFbo->getFboId();
}
void GlComposeTask::onResolve() {
GLuint GlComposeTask::getResolveFboId()
{
return mFbo->getResolveFboId();
}
void GlComposeTask::onResolve()
{
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, getSelfFbo()));
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, getResolveFboId()));
GL_CHECK(glBlitFramebuffer(0, 0, mRenderWidth, mRenderHeight, 0, 0, mRenderWidth, mRenderHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST));
}
/************************************************************************/
/* GlBlitTask Class Implementation */
/************************************************************************/
GlBlitTask::GlBlitTask(GlProgram* program, GLuint target, GlRenderTarget* fbo, Array<GlRenderTask*>&& tasks)
: GlComposeTask(program, target, fbo, std::move(tasks)), mColorTex(fbo->getColorTexture())
{
}
void GlBlitTask::run()
{
GlComposeTask::run();
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo()));
GL_CHECK(glViewport(mTargetViewport.x, mTargetViewport.y, mTargetViewport.w, mTargetViewport.h));
GL_CHECK(glViewport(mTargetViewport.x(), mTargetViewport.y(), mTargetViewport.w(), mTargetViewport.h()));
if (mClearBuffer) {
GL_CHECK(glClearColor(0, 0, 0, 0));
@ -246,16 +278,24 @@ void GlBlitTask::run()
GlRenderTask::run();
}
/************************************************************************/
/* GlDrawBlitTask Class Implementation */
/************************************************************************/
GlDrawBlitTask::GlDrawBlitTask(GlProgram* program, GLuint target, GlRenderTarget* fbo, Array<GlRenderTask*>&& tasks)
: GlComposeTask(program, target, fbo, std::move(tasks))
{
}
GlDrawBlitTask::~GlDrawBlitTask()
{
if (mPrevTask) delete mPrevTask;
}
void GlDrawBlitTask::run()
{
if (mPrevTask) mPrevTask->run();
@ -269,15 +309,22 @@ void GlDrawBlitTask::run()
GlRenderTask::run();
}
/************************************************************************/
/* GlClipTask Class Implementation */
/************************************************************************/
GlClipTask::GlClipTask(GlRenderTask* clip, GlRenderTask* mask)
:GlRenderTask(nullptr), mClipTask(clip), mMaskTask(mask) {}
GlClipTask::~GlClipTask()
{
delete mClipTask;
delete mMaskTask;
}
void GlClipTask::run()
{
GL_CHECK(glEnable(GL_STENCIL_TEST));
@ -304,14 +351,22 @@ void GlClipTask::run()
GL_CHECK(glDisable(GL_STENCIL_TEST));
}
void GlClipTask::normalizeDrawDepth(int32_t maxDepth)
{
mClipTask->normalizeDrawDepth(maxDepth);
mMaskTask->normalizeDrawDepth(maxDepth);
}
/************************************************************************/
/* GlSimpleBlendTask Class Implementation */
/************************************************************************/
GlSimpleBlendTask::GlSimpleBlendTask(BlendMethod method, GlProgram* program)
: GlRenderTask(program), mBlendMethod(method) {}
: GlRenderTask(program), mBlendMethod(method)
{
}
void GlSimpleBlendTask::run()
{
@ -332,8 +387,17 @@ void GlSimpleBlendTask::run()
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
/************************************************************************/
/* GlComplexBlendTask Class Implementation */
/************************************************************************/
GlComplexBlendTask::GlComplexBlendTask(GlProgram* program, GlRenderTarget* dstFbo, GlRenderTarget* dstCopyFbo, GlRenderTask* stencilTask, GlComposeTask* composeTask)
: GlRenderTask(program), mDstFbo(dstFbo), mDstCopyFbo(dstCopyFbo), mStencilTask(stencilTask), mComposeTask(composeTask) {}
: GlRenderTask(program), mDstFbo(dstFbo), mDstCopyFbo(dstCopyFbo), mStencilTask(stencilTask), mComposeTask(composeTask)
{
}
GlComplexBlendTask::~GlComplexBlendTask()
{
@ -341,6 +405,7 @@ GlComplexBlendTask::~GlComplexBlendTask()
delete mComposeTask;
}
void GlComplexBlendTask::run()
{
mComposeTask->run();
@ -349,12 +414,11 @@ void GlComplexBlendTask::run()
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, mDstFbo->getFboId()));
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDstCopyFbo->getResolveFboId()));
GL_CHECK(glViewport(0, 0, mDstFbo->getViewport().w, mDstFbo->getViewport().h));
GL_CHECK(glScissor(0, 0, mDstFbo->getViewport().w, mDstFbo->getViewport().h));
GL_CHECK(glViewport(0, 0, mDstFbo->getViewport().w(), mDstFbo->getViewport().h()));
GL_CHECK(glScissor(0, 0, mDstFbo->getViewport().w(), mDstFbo->getViewport().h()));
const auto& vp = getViewport();
GL_CHECK(glBlitFramebuffer(vp.x, vp.y, vp.x + vp.w, vp.y + vp.h, 0, 0, vp.w, vp.h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
GL_CHECK(glBlitFramebuffer(vp.min.x, vp.min.y, vp.max.x, vp.max.y, 0, 0, vp.w(), vp.h(), GL_COLOR_BUFFER_BIT, GL_LINEAR));
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstFbo->getFboId()));
@ -381,12 +445,17 @@ void GlComplexBlendTask::run()
GL_CHECK(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
}
void GlComplexBlendTask::normalizeDrawDepth(int32_t maxDepth)
{
mStencilTask->normalizeDrawDepth(maxDepth);
GlRenderTask::normalizeDrawDepth(maxDepth);
}
/************************************************************************/
/* GlGaussianBlurTask Class Implementation */
/************************************************************************/
void GlGaussianBlurTask::run()
{
const auto vp = getViewport();
@ -439,6 +508,9 @@ void GlGaussianBlurTask::run()
GL_CHECK(glEnable(GL_BLEND));
}
/************************************************************************/
/* GlEffectDropShadowTask Class Implementation */
/************************************************************************/
void GlEffectDropShadowTask::run()
{
@ -492,6 +564,9 @@ void GlEffectDropShadowTask::run()
GL_CHECK(glEnable(GL_BLEND));
}
/************************************************************************/
/* GlEffectColorTransformTask Class Implementation */
/************************************************************************/
void GlEffectColorTransformTask::run()
{

View file

@ -130,12 +130,10 @@ public:
protected:
GLuint getTargetFbo() { return mTargetFbo; }
GLuint getSelfFbo();
GLuint getResolveFboId();
void onResolve();
private:
GLuint mTargetFbo;
GlRenderTarget* mFbo;

View file

@ -52,6 +52,8 @@ void GlRenderer::flush()
{
clearDisposes();
mRootTarget.reset();
ARRAY_FOREACH(p, mComposePool) delete(*p);
mComposePool.clear();
@ -181,10 +183,10 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const RenderColor& c, RenderUpdat
bbox.intersect(vp);
}
auto x = bbox.x - vp.x;
auto y = bbox.y - vp.y;
auto w = bbox.w;
auto h = bbox.h;
auto x = bbox.sx() - vp.sx();
auto y = bbox.sy() - vp.sy();
auto w = bbox.sw();
auto h = bbox.sh();
GlRenderTask* task = nullptr;
if (mBlendMethod != BlendMethod::Normal && !complexBlend) task = new GlSimpleBlendTask(mBlendMethod, mPrograms[RT_Color]);
@ -197,7 +199,8 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const RenderColor& c, RenderUpdat
return;
}
task->setViewport({x, vp.h - y - h, w, h});
y = vp.sh() - y - h;
task->setViewport({{x, y}, {x + w, y + h}});
GlRenderTask* stencilTask = nullptr;
@ -266,7 +269,6 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFla
{
auto vp = currentPass()->getViewport();
auto bbox = sdata.geometry.viewport;
bbox.intersect(vp);
const Fill::ColorStop* stops = nullptr;
@ -275,13 +277,9 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFla
GlRenderTask* task = nullptr;
if (fill->type() == Type::LinearGradient) {
task = new GlRenderTask(mPrograms[RT_LinGradient]);
} else if (fill->type() == Type::RadialGradient) {
task = new GlRenderTask(mPrograms[RT_RadGradient]);
} else {
return;
}
if (fill->type() == Type::LinearGradient) task = new GlRenderTask(mPrograms[RT_LinGradient]);
else if (fill->type() == Type::RadialGradient) task = new GlRenderTask(mPrograms[RT_RadGradient]);
else return;
task->setDrawDepth(depth);
@ -291,13 +289,11 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFla
}
auto complexBlend = beginComplexBlending(bbox, sdata.geometry.getBounds());
if (complexBlend) vp = currentPass()->getViewport();
auto x = bbox.x - vp.x;
auto y = bbox.y - vp.y;
task->setViewport({x, vp.h - y - bbox.h, bbox.w, bbox.h});
auto x = bbox.sx() - vp.sx();
auto y = vp.sh() - (bbox.sy() - vp.sy()) - bbox.sh();
task->setViewport({{x, y}, {x + bbox.sw(), y + bbox.sh()}});
GlRenderTask* stencilTask = nullptr;
GlStencilMode stencilMode = sdata.geometry.getStencilMode(flag);
@ -496,22 +492,18 @@ void GlRenderer::drawClip(Array<RenderData>& clips)
for (uint32_t i = 0; i < clips.count; ++i) {
auto sdata = static_cast<GlShape*>(clips[i]);
auto clipTask = new GlRenderTask(mPrograms[RT_Stencil]);
clipTask->setDrawDepth(clipDepths[i]);
auto flag = (sdata->geometry.stroke.vertex.count > 0) ? RenderUpdateFlag::Stroke : RenderUpdateFlag::Path;
sdata->geometry.draw(clipTask, &mGpuBuffer, flag);
auto bbox = sdata->geometry.viewport;
bbox.intersect(vp);
auto x = bbox.x - vp.x;
auto y = bbox.y - vp.y;
clipTask->setViewport({x, vp.h - y - bbox.h, bbox.w, bbox.h});
auto x = bbox.sx() - vp.sx();
auto y = vp.sh() - (bbox.sy() - vp.sy()) - bbox.sh();
clipTask->setViewport({{x, y}, {x + bbox.sw(), y + bbox.sh()}});
float matrix44[16];
currentPass()->getMatrix(matrix44, sdata->geometry.matrix);
@ -527,7 +519,7 @@ void GlRenderer::drawClip(Array<RenderData>& clips)
maskTask->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), identityVertexOffset});
maskTask->addBindResource(GlBindingResource{0, loc, mGpuBuffer.getBufferId(), mat4Offset, 16 * sizeof(float), });
maskTask->setDrawRange(identityIndexOffset, 6);
maskTask->setViewport({0, 0, static_cast<int32_t>(vp.w), static_cast<int32_t>(vp.h)});
maskTask->setViewport({{0, 0}, {vp.sw(), vp.sh()}});
currentPass()->addRenderTask(new GlClipTask(clipTask, maskTask));
}
@ -541,11 +533,10 @@ GlRenderPass* GlRenderer::currentPass()
bool GlRenderer::beginComplexBlending(const RenderRegion& vp, RenderRegion bounds)
{
if (vp.w == 0 || vp.h == 0) return false;
if (vp.invalid()) return false;
bounds.intersect(vp);
if (bounds.w == 0 || bounds.h == 0) return false;
if (bounds.invalid()) return false;
if (mBlendMethod == BlendMethod::Normal || mBlendMethod == BlendMethod::Add || mBlendMethod == BlendMethod::Darken || mBlendMethod == BlendMethod::Lighten) return false;
@ -571,20 +562,12 @@ void GlRenderer::endBlendingCompose(GlRenderTask* stencilTask, const Matrix& mat
if (mBlendPool.count < 2) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h));
auto dstCopyFbo = mBlendPool[1]->getRenderTarget(vp);
{
const auto& passVp = currentPass()->getViewport();
auto x = vp.x;
auto y = vp.y;
auto w = vp.w;
auto h = vp.h;
stencilTask->setViewport({x, passVp.h - y - h, w, h});
}
auto x = vp.sx();
auto y = currentPass()->getViewport().sh() - vp.sy() - vp.sh();
stencilTask->setViewport({{x, y}, {x + vp.sw(), y + vp.sh()}});
stencilTask->setDrawDepth(currentPass()->nextDrawDepth());
{
// set view matrix
float matrix44[16];
currentPass()->getMatrix(matrix44, matrix);
@ -596,12 +579,9 @@ void GlRenderer::endBlendingCompose(GlRenderTask* stencilTask, const Matrix& mat
viewOffset,
16 * sizeof(float),
});
}
auto task = new GlComplexBlendTask(getBlendProgram(), currentPass()->getFbo(), dstCopyFbo, stencilTask, composeTask);
prepareCmpTask(task, vp, blendPass->getFboWidth(), blendPass->getFboHeight());
task->setDrawDepth(currentPass()->nextDrawDepth());
// src and dst texture
@ -632,8 +612,7 @@ GlProgram* GlRenderer::getBlendProgram()
void GlRenderer::prepareBlitTask(GlBlitTask* task)
{
RenderRegion region{0, 0, static_cast<int32_t>(surface.w), static_cast<int32_t>(surface.h)};
prepareCmpTask(task, region, surface.w, surface.h);
prepareCmpTask(task, {{0, 0}, {int32_t(surface.w), int32_t(surface.h)}}, surface.w, surface.h);
task->addBindResource(GlBindingResource{0, task->getColorTexture(), task->getProgram()->getUniformLocation("uSrcTexture")});
}
@ -648,13 +627,13 @@ void GlRenderer::prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint
auto taskVp = vp;
taskVp.intersect(passVp);
auto x = taskVp.x - passVp.x;
auto y = taskVp.y - passVp.y;
auto w = taskVp.w;
auto h = taskVp.h;
auto x = taskVp.sx() - passVp.sx();
auto y = taskVp.sy() - passVp.sy();
auto w = taskVp.sw();
auto h = taskVp.sh();
float rw = static_cast<float>(passVp.w);
float rh = static_cast<float>(passVp.h);
float rw = static_cast<float>(passVp.w());
float rh = static_cast<float>(passVp.h());
float l = static_cast<float>(x);
float t = static_cast<float>(rh - y);
@ -705,10 +684,9 @@ void GlRenderer::prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint
task->addVertexLayout(GlVertexLayout{0, 2, 4 * sizeof(float), vertexOffset});
task->addVertexLayout(GlVertexLayout{1, 2, 4 * sizeof(float), vertexOffset + 2 * sizeof(float)});
task->setDrawRange(indexOffset, indices.count);
task->setViewport({x, static_cast<int32_t>((passVp.h - y - h)), w, h});
y = (passVp.sh() - y - h);
task->setViewport({{x, y}, {x + w, y + h}});
}
@ -741,11 +719,11 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
if (program && !selfPass->isEmpty() && !maskPass->isEmpty()) {
auto prev_task = maskPass->endRenderPass<GlComposeTask>(nullptr, currentPass()->getFboId());
prev_task->setDrawDepth(currentPass()->nextDrawDepth());
prev_task->setRenderSize(static_cast<uint32_t>(glCmp->bbox.w), static_cast<uint32_t>(glCmp->bbox.h));
prev_task->setRenderSize(glCmp->bbox.w(), glCmp->bbox.h());
prev_task->setViewport(glCmp->bbox);
auto compose_task = selfPass->endRenderPass<GlDrawBlitTask>(program, currentPass()->getFboId());
compose_task->setRenderSize(static_cast<uint32_t>(glCmp->bbox.w), static_cast<uint32_t>(glCmp->bbox.h));
compose_task->setRenderSize(glCmp->bbox.w(), glCmp->bbox.h());
compose_task->setPrevTask(prev_task);
prepareCmpTask(compose_task, glCmp->bbox, selfPass->getFboWidth(), selfPass->getFboHeight());
@ -754,7 +732,7 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
compose_task->addBindResource(GlBindingResource{1, maskPass->getTextureId(), program->getUniformLocation("uMaskTexture")});
compose_task->setDrawDepth(currentPass()->nextDrawDepth());
compose_task->setParentSize(static_cast<uint32_t>(currentPass()->getViewport().w), static_cast<uint32_t>(currentPass()->getViewport().h));
compose_task->setParentSize(currentPass()->getViewport().w(), currentPass()->getViewport().h());
currentPass()->addRenderTask(compose_task);
}
@ -767,7 +745,7 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
if (!renderPass->isEmpty()) {
auto task = renderPass->endRenderPass<GlDrawBlitTask>(mPrograms[RT_Image], currentPass()->getFboId());
task->setRenderSize(static_cast<uint32_t>(glCmp->bbox.w), static_cast<uint32_t>(glCmp->bbox.h));
task->setRenderSize(glCmp->bbox.w(), glCmp->bbox.h());
prepareCmpTask(task, glCmp->bbox, renderPass->getFboWidth(), renderPass->getFboHeight());
task->setDrawDepth(currentPass()->nextDrawDepth());
@ -795,7 +773,7 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
// texture id
task->addBindResource(GlBindingResource{0, renderPass->getTextureId(), task->getProgram()->getUniformLocation("uTexture")});
task->setParentSize(static_cast<uint32_t>(currentPass()->getViewport().w), static_cast<uint32_t>(currentPass()->getViewport().h));
task->setParentSize(currentPass()->getViewport().w(), currentPass()->getViewport().h());
currentPass()->addRenderTask(std::move(task));
}
delete(renderPass);
@ -809,6 +787,8 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
bool GlRenderer::clear()
{
if (mRootTarget.invalid()) return false;
mClearBuffer = true;
return true;
}
@ -832,9 +812,8 @@ bool GlRenderer::target(void* context, int32_t id, uint32_t w, uint32_t h)
currentContext();
mRootTarget = GlRenderTarget(surface.w, surface.h);
mRootTarget.setViewport({0, 0, static_cast<int32_t>(surface.w), static_cast<int32_t>(surface.h)});
mRootTarget.init(mTargetFboId);
mRootTarget.setViewport({{0, 0}, {int32_t(surface.w), int32_t(surface.h)}});
mRootTarget.init(surface.w, surface.h, mTargetFboId);
return true;
}
@ -861,7 +840,7 @@ bool GlRenderer::sync()
prepareBlitTask(task);
task->mClearBuffer = mClearBuffer;
task->setTargetViewport({0, 0, static_cast<int32_t>(surface.w), static_cast<int32_t>(surface.h)});
task->setTargetViewport({{0, 0}, {int32_t(surface.w), int32_t(surface.h)}});
if (mGpuBuffer.flushToGPU()) {
mGpuBuffer.bind();
@ -874,6 +853,9 @@ bool GlRenderer::sync()
clearDisposes();
// Reset clear buffer flag to default (false) after use.
mClearBuffer = false;
delete task;
return true;
@ -882,7 +864,7 @@ bool GlRenderer::sync()
RenderRegion GlRenderer::region(RenderData data)
{
if (currentPass()->isEmpty()) return {0, 0, 0, 0};
if (currentPass()->isEmpty()) return {};
auto shape = reinterpret_cast<GlShape*>(data);
auto bounds = shape->geometry.getBounds();
@ -896,6 +878,8 @@ RenderRegion GlRenderer::region(RenderData data)
bool GlRenderer::preRender()
{
if (mRootTarget.invalid()) return false;
currentContext();
if (mPrograms.empty()) initShaders();
mRenderPassStack.push(new GlRenderPass(&mRootTarget));
@ -930,20 +914,11 @@ bool GlRenderer::beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_
cmp->opacity = opacity;
uint32_t index = mRenderPassStack.count - 1;
if (index >= mComposePool.count) {
mComposePool.push( new GlRenderTargetPool(surface.w, surface.h));
}
if (index >= mComposePool.count) mComposePool.push( new GlRenderTargetPool(surface.w, surface.h));
auto glCmp = static_cast<GlCompositor*>(cmp);
if (glCmp->bbox.w > 0 && glCmp->bbox.h > 0) {
auto renderTarget = mComposePool[index]->getRenderTarget(glCmp->bbox);
mRenderPassStack.push(new GlRenderPass(renderTarget));
} else {
// empty render pass
mRenderPassStack.push(new GlRenderPass(nullptr));
}
if (glCmp->bbox.valid()) mRenderPassStack.push(new GlRenderPass(mComposePool[index]->getRenderTarget(glCmp->bbox)));
else mRenderPassStack.push(new GlRenderPass(nullptr));
return true;
}
@ -1064,12 +1039,12 @@ bool GlRenderer::effectGaussianBlurRegion(RenderEffectGaussianBlur* effect)
{
auto gaussianBlur = (GlGaussianBlur*)effect->rd;
if (effect->direction != 2) {
effect->extend.x = -gaussianBlur->extend;
effect->extend.w = +gaussianBlur->extend * 2;
effect->extend.min.x = -gaussianBlur->extend;
effect->extend.max.x = +gaussianBlur->extend;
}
if (effect->direction != 1) {
effect->extend.y = -gaussianBlur->extend;
effect->extend.h = +gaussianBlur->extend * 2;
effect->extend.min.y = -gaussianBlur->extend;
effect->extend.max.y = +gaussianBlur->extend;
}
return true;
};
@ -1078,10 +1053,10 @@ bool GlRenderer::effectGaussianBlurRegion(RenderEffectGaussianBlur* effect)
bool GlRenderer::effectDropShadowRegion(RenderEffectDropShadow* effect)
{
auto gaussianBlur = (GlDropShadow*)effect->rd;
effect->extend.x = -gaussianBlur->extend;
effect->extend.w = +gaussianBlur->extend * 2;
effect->extend.y = -gaussianBlur->extend;
effect->extend.h = +gaussianBlur->extend * 2;
effect->extend.min.x = -gaussianBlur->extend;
effect->extend.max.x = +gaussianBlur->extend;
effect->extend.min.y = -gaussianBlur->extend;
effect->extend.max.y = +gaussianBlur->extend;
return true;
};
@ -1131,7 +1106,6 @@ bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* ef
auto voffset = mGpuBuffer.push((void*)vdata, sizeof(vdata));
auto ioffset = mGpuBuffer.pushIndex((void*)idata, sizeof(idata));
// effect gaussian blur
if (effect->type == SceneEffect::GaussianBlur) {
// get gaussian programs
GlProgram* programHorz = mPrograms[RT_GaussianHorz];
@ -1147,7 +1121,7 @@ bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* ef
// create gaussian blur tasks
auto gaussianTask = new GlGaussianBlurTask(dstFbo, dstCopyFbo0, dstCopyFbo1);
gaussianTask->effect = (RenderEffectGaussianBlur*)effect;
gaussianTask->setViewport({0, 0, vp.w, vp.h});
gaussianTask->setViewport({{0, 0}, {vp.sw(), vp.sh()}});
// horizontal blur task and geometry
gaussianTask->horzTask = new GlRenderTask(programHorz);
gaussianTask->horzTask->addBindResource(GlBindingResource{0, programHorz->getUniformBlockIndex("Gaussian"), mGpuBuffer.getBufferId(), blurOffset, sizeof(GlGaussianBlur)});
@ -1160,8 +1134,7 @@ bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* ef
gaussianTask->vertTask->setDrawRange(ioffset, 6);
// add task to render pipeline
pass->addRenderTask(gaussianTask);
} // effect drop shadow
else if (effect->type == SceneEffect::DropShadow) {
} else if (effect->type == SceneEffect::DropShadow) {
// get programs
GlProgram* program = mPrograms[RT_DropShadow];
GlProgram* programHorz = mPrograms[RT_GaussianHorz];
@ -1177,7 +1150,7 @@ bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* ef
// create gaussian blur tasks
auto task = new GlEffectDropShadowTask(program, dstFbo, dstCopyFbo0, dstCopyFbo1);
task->effect = (RenderEffectDropShadow*)effect;
task->setViewport({0, 0, vp.w, vp.h});
task->setViewport({{0, 0}, {vp.sw(), vp.sh()}});
task->addBindResource(GlBindingResource{0, program->getUniformBlockIndex("DropShadow"), mGpuBuffer.getBufferId(), paramsOffset, sizeof(GlDropShadow)});
task->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset});
task->setDrawRange(ioffset, 6);
@ -1193,8 +1166,7 @@ bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* ef
task->vertTask->setDrawRange(ioffset, 6);
// add task to render pipeline
pass->addRenderTask(task);
} // effect fill, tint, tritone
else if ((effect->type == SceneEffect::Fill) || (effect->type == SceneEffect::Tint) || (effect->type == SceneEffect::Tritone)) {
} else if ((effect->type == SceneEffect::Fill) || (effect->type == SceneEffect::Tint) || (effect->type == SceneEffect::Tritone)) {
GlProgram* program{};
if (effect->type == SceneEffect::Fill) program = mPrograms[RT_EffectFill];
else if (effect->type == SceneEffect::Tint) program = mPrograms[RT_EffectTint];
@ -1208,7 +1180,7 @@ bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* ef
auto paramsOffset = mGpuBuffer.push(params, sizeof(GlEffectParams), true);
// create and setup task
auto task = new GlEffectColorTransformTask(program, dstFbo, dstCopyFbo);
task->setViewport({0, 0, vp.w, vp.h});
task->setViewport({{0, 0}, {vp.sw(), vp.sh()}});
task->addBindResource(GlBindingResource{0, program->getUniformBlockIndex("Params"), mGpuBuffer.getBufferId(), paramsOffset, sizeof(GlEffectParams)});
task->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset});
task->setDrawRange(ioffset, 6);
@ -1251,25 +1223,18 @@ bool GlRenderer::blend(BlendMethod method)
bool GlRenderer::renderImage(void* data)
{
auto sdata = static_cast<GlShape*>(data);
if (!sdata) return false;
if (currentPass()->isEmpty()) return true;
if (currentPass()->isEmpty() || !(sdata->updateFlag & RenderUpdateFlag::Image)) return true;
if ((sdata->updateFlag & RenderUpdateFlag::Image) == 0) return true;
auto vp = currentPass()->getViewport();
auto bbox = sdata->geometry.viewport;
bbox.intersect(vp);
if (bbox.invalid()) return true;
if (bbox.w <= 0 || bbox.h <= 0) return true;
auto x = bbox.x - vp.x;
auto y = bbox.y - vp.y;
int32_t drawDepth = currentPass()->nextDrawDepth();
auto x = bbox.sx() - vp.sx();
auto y = bbox.sy() - vp.sy();
auto drawDepth = currentPass()->nextDrawDepth();
if (!sdata->clips.empty()) drawClip(sdata->clips);
@ -1282,7 +1247,6 @@ bool GlRenderer::renderImage(void* data)
}
bool complexBlend = beginComplexBlending(bbox, sdata->geometry.getBounds());
if (complexBlend) vp = currentPass()->getViewport();
// matrix buffer
@ -1311,7 +1275,11 @@ bool GlRenderer::renderImage(void* data)
// texture id
task->addBindResource(GlBindingResource{0, sdata->texId, task->getProgram()->getUniformLocation("uTexture")});
task->setViewport({x, vp.h - y - bbox.h, bbox.w, bbox.h});
y = vp.sh() - y - bbox.sh();
auto x2 = x + bbox.sw();
auto y2 = y + bbox.sh();
task->setViewport({{x, y}, {x2, y2}});
currentPass()->addRenderTask(task);
@ -1327,33 +1295,25 @@ bool GlRenderer::renderImage(void* data)
bool GlRenderer::renderShape(RenderData data)
{
auto sdata = static_cast<GlShape*>(data);
if (currentPass()->isEmpty()) return true;
auto sdata = static_cast<GlShape*>(data);
if (sdata->updateFlag == RenderUpdateFlag::None) return true;
const auto& vp = currentPass()->getViewport();
auto bbox = sdata->geometry.viewport;
bbox.intersect(vp);
if (bbox.w <= 0 || bbox.h <= 0) return true;
bbox.intersect(currentPass()->getViewport());
if (bbox.invalid()) return true;
int32_t drawDepth1 = 0, drawDepth2 = 0;
size_t flags = static_cast<size_t>(sdata->updateFlag);
if (flags == 0) return false;
if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Color)) drawDepth1 = currentPass()->nextDrawDepth();
if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::GradientStroke)) drawDepth2 = currentPass()->nextDrawDepth();
if (sdata->updateFlag == RenderUpdateFlag::None) return false;
if (sdata->updateFlag & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Color)) drawDepth1 = currentPass()->nextDrawDepth();
if (sdata->updateFlag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::GradientStroke)) drawDepth2 = currentPass()->nextDrawDepth();
if (!sdata->clips.empty()) drawClip(sdata->clips);
auto processFill = [&]() {
if (flags & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient)) {
if (sdata->updateFlag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient)) {
if (const auto& gradient = sdata->rshape->fill) {
drawPrimitive(*sdata, gradient, RenderUpdateFlag::Gradient, drawDepth1);
} else if (sdata->rshape->color.a > 0) {
@ -1364,7 +1324,7 @@ bool GlRenderer::renderShape(RenderData data)
auto processStroke = [&]() {
if (!sdata->rshape->stroke) return;
if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::GradientStroke)) {
if (sdata->updateFlag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::GradientStroke)) {
if (const auto& gradient = sdata->rshape->strokeFill()) {
drawPrimitive(*sdata, gradient, RenderUpdateFlag::GradientStroke, drawDepth2);
} else if (sdata->rshape->stroke->color.a > 0) {
@ -1525,6 +1485,8 @@ bool GlRenderer::viewport(const RenderRegion& vp)
bool GlRenderer::preUpdate()
{
if (mRootTarget.invalid()) return false;
currentContext();
return true;
}

View file

@ -153,7 +153,7 @@ private:
} mDisposed;
BlendMethod mBlendMethod = BlendMethod::Normal;
bool mClearBuffer = true; //FIXME: clear buffer should be optional (default is false)
bool mClearBuffer = false;
};
#endif /* _TVG_GL_RENDERER_H_ */

View file

@ -1516,12 +1516,7 @@ void Stroker::stroke(const RenderShape *rshape, const RenderPath& path)
RenderRegion Stroker::bounds() const
{
return RenderRegion {
static_cast<int32_t>(floor(mLeftTop.x)),
static_cast<int32_t>(floor(mLeftTop.y)),
static_cast<int32_t>(ceil(mRightBottom.x - floor(mLeftTop.x))),
static_cast<int32_t>(ceil(mRightBottom.y - floor(mLeftTop.y))),
};
return {{int32_t(floor(mLeftTop.x)), int32_t(floor(mLeftTop.y))}, {int32_t(ceil(mRightBottom.x)), int32_t(ceil(mRightBottom.y))}};
}
@ -2197,26 +2192,15 @@ void BWTessellator::tessellate(const RenderPath& path, const Matrix& matrix)
RenderRegion BWTessellator::bounds() const
{
return RenderRegion {
static_cast<int32_t>(floor(bbox.min.x)),
static_cast<int32_t>(floor(bbox.min.y)),
static_cast<int32_t>(ceil(bbox.max.x - floor(bbox.min.x))),
static_cast<int32_t>(ceil(bbox.max.y - floor(bbox.min.y))),
};
return {{int32_t(floor(bbox.min.x)), int32_t(floor(bbox.min.y))}, {int32_t(ceil(bbox.max.x)), int32_t(ceil(bbox.max.y))}};
}
uint32_t BWTessellator::pushVertex(float x, float y)
{
auto index = _pushVertex(mBuffer->vertex, x, y);
if (index == 0) {
bbox.max = bbox.min = {x, y};
} else {
bbox.min = {std::min(bbox.min.x, x), std::min(bbox.min.y, y)};
bbox.max = {std::max(bbox.max.x, x), std::max(bbox.max.y, y)};
}
if (index == 0) bbox.max = bbox.min = {x, y};
else bbox = {{std::min(bbox.min.x, x), std::min(bbox.min.y, y)}, {std::max(bbox.max.x, x), std::max(bbox.max.y, y)}};
return index;
}

View file

@ -156,7 +156,7 @@ private:
void pushTriangle(uint32_t a, uint32_t b, uint32_t c);
GlGeometryBuffer* mBuffer;
BBox bbox = {{}, {}};
BBox bbox = {};
};
} // namespace tvg

View file

@ -121,19 +121,20 @@ struct SwSpan
struct SwRle
{
SwSpan *spans;
uint32_t alloc;
uint32_t size;
};
Array<SwSpan> spans;
struct SwBBox
{
SwPoint min, max;
void reset()
bool invalid() const
{
min.x = min.y = max.x = max.y = 0;
return spans.empty();
}
bool valid() const
{
return !invalid();
}
uint32_t size() const { return spans.count; }
SwSpan* data() const { return spans.data; }
};
struct SwFill
@ -215,7 +216,7 @@ struct SwShape
SwFill* fill = nullptr;
SwRle* rle = nullptr;
SwRle* strokeRle = nullptr;
SwBBox bbox; //Keep it boundary without stroke region. Using for optimal filling.
RenderRegion bbox; //Keep it boundary without stroke region. Using for optimal filling.
bool fastTrack = false; //Fast Track: axis-aligned rectangle without any clips?
};
@ -279,7 +280,7 @@ struct SwCompositor : RenderCompositor
SwSurface* recoverSfc; //Recover surface when composition is started
SwCompositor* recoverCmp; //Recover compositor when composition is done
SwImage image;
SwBBox bbox;
RenderRegion bbox;
bool valid;
};
@ -499,16 +500,15 @@ SwFixed mathLength(const SwPoint& pt);
int mathCubicAngle(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
SwFixed mathMean(SwFixed angle1, SwFixed angle2);
SwPoint mathTransform(const Point* to, const Matrix& transform);
bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
bool mathClipBBox(const SwBBox& clipper, SwBBox& clippee);
bool mathUpdateOutlineBBox(const SwOutline* outline, const RenderRegion& clipBox, RenderRegion& renderBox, bool fastTrack);
void shapeReset(SwShape* shape);
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite);
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const RenderRegion& clipBox, RenderRegion& renderBox, SwMpool* mpool, unsigned tid, bool hasComposite);
bool shapePrepared(const SwShape* shape);
bool shapeGenRle(SwShape* shape, const RenderShape* rshape, bool antiAlias);
void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid);
void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix& transform);
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const RenderRegion& clipBox, RenderRegion& renderBox, SwMpool* mpool, unsigned tid);
void shapeFree(SwShape* shape);
void shapeDelStroke(SwShape* shape);
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable);
@ -523,8 +523,8 @@ bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline);
SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid);
void strokeFree(SwStroke* stroke);
bool imagePrepare(SwImage* image, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias);
bool imagePrepare(SwImage* image, const Matrix& transform, const RenderRegion& clipBox, RenderRegion& renderBox, SwMpool* mpool, unsigned tid);
bool imageGenRle(SwImage* image, const RenderRegion& bbox, bool antiAlias);
void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid);
void imageReset(SwImage* image);
void imageFree(SwImage* image);
@ -547,13 +547,13 @@ void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //matting ver.
SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
SwRle* rleRender(const SwBBox* bbox);
SwRle* rleRender(SwRle* rle, const SwOutline* outline, const RenderRegion& bbox, bool antiAlias);
SwRle* rleRender(const RenderRegion* bbox);
void rleFree(SwRle* rle);
void rleReset(SwRle* rle);
void rleMerge(SwRle* rle, SwRle* clip1, SwRle* clip2);
bool rleClip(SwRle* rle, const SwRle* clip);
bool rleClip(SwRle* rle, const SwBBox* clip);
bool rleClip(SwRle* rle, const RenderRegion* clip);
SwMpool* mpoolInit(uint32_t threads);
bool mpoolTerm(SwMpool* mpool);
@ -568,7 +568,11 @@ void mpoolRetDashOutline(SwMpool* mpool, unsigned idx);
bool rasterCompositor(SwSurface* surface);
bool rasterGradientShape(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity);
bool rasterShape(SwSurface* surface, SwShape* shape, RenderColor& c);
bool rasterImage(SwSurface* surface, SwImage* image, const Matrix& transform, const SwBBox& bbox, uint8_t opacity);
bool rasterTexmapPolygon(SwSurface* surface, const SwImage& image, const Matrix& transform, const RenderRegion& bbox, uint8_t opacity);
bool rasterScaledImage(SwSurface* surface, const SwImage& image, const Matrix& transform, const RenderRegion& bbox, uint8_t opacity);
bool rasterDirectImage(SwSurface* surface, const SwImage& image, const RenderRegion& bbox, uint8_t opacity);
bool rasterScaledRleImage(SwSurface* surface, const SwImage& image, const Matrix& transform, const RenderRegion& bbox, uint8_t opacity);
bool rasterDirectRleImage(SwSurface* surface, const SwImage& image, uint8_t opacity);
bool rasterStroke(SwSurface* surface, SwShape* shape, RenderColor& c);
bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity);
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val = 0);
@ -576,7 +580,7 @@ void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
void rasterTranslucentPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity);
void rasterPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity);
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);
void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped);
void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const RenderRegion& bbox, bool flipped);
void rasterUnpremultiply(RenderSurface* surface);
void rasterPremultiply(RenderSurface* surface);
bool rasterConvertCS(RenderSurface* surface, ColorSpace to);

View file

@ -72,7 +72,7 @@ static bool _genOutline(SwImage* image, const Matrix& transform, SwMpool* mpool,
/* External Class Implementation */
/************************************************************************/
bool imagePrepare(SwImage* image, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
bool imagePrepare(SwImage* image, const Matrix& transform, const RenderRegion& clipBox, RenderRegion& renderBox, SwMpool* mpool, unsigned tid)
{
image->direct = _onlyShifted(transform);
@ -91,13 +91,13 @@ bool imagePrepare(SwImage* image, const Matrix& transform, const SwBBox& clipReg
}
if (!_genOutline(image, transform, mpool, tid)) return false;
return mathUpdateOutlineBBox(image->outline, clipRegion, renderRegion, image->direct);
return mathUpdateOutlineBBox(image->outline, clipBox, renderBox, image->direct);
}
bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias)
bool imageGenRle(SwImage* image, const RenderRegion& renderBox, bool antiAlias)
{
if ((image->rle = rleRender(image->rle, image->outline, renderRegion, antiAlias))) return true;
if ((image->rle = rleRender(image->rle, image->outline, renderBox, antiAlias))) return true;
return false;
}

View file

@ -271,27 +271,12 @@ SwPoint mathTransform(const Point* to, const Matrix& transform)
}
bool mathClipBBox(const SwBBox& clipper, SwBBox& clippee)
{
clippee.max.x = (clippee.max.x < clipper.max.x) ? clippee.max.x : clipper.max.x;
clippee.max.y = (clippee.max.y < clipper.max.y) ? clippee.max.y : clipper.max.y;
clippee.min.x = (clippee.min.x > clipper.min.x) ? clippee.min.x : clipper.min.x;
clippee.min.y = (clippee.min.y > clipper.min.y) ? clippee.min.y : clipper.min.y;
//Check boundary
if (clippee.min.x >= clipper.max.x || clippee.min.y >= clipper.max.y ||
clippee.max.x <= clipper.min.x || clippee.max.y <= clipper.min.y) return false;
return true;
}
bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack)
bool mathUpdateOutlineBBox(const SwOutline* outline, const RenderRegion& clipBox, RenderRegion& renderBox, bool fastTrack)
{
if (!outline) return false;
if (outline->pts.empty() || outline->cntrs.empty()) {
renderRegion.reset();
renderBox.reset();
return false;
}
@ -310,16 +295,13 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S
}
if (fastTrack) {
renderRegion.min.x = static_cast<SwCoord>(round(xMin / 64.0f));
renderRegion.max.x = static_cast<SwCoord>(round(xMax / 64.0f));
renderRegion.min.y = static_cast<SwCoord>(round(yMin / 64.0f));
renderRegion.max.y = static_cast<SwCoord>(round(yMax / 64.0f));
renderBox.min = {int32_t(round(xMin / 64.0f)), int32_t(round(yMin / 64.0f))};
renderBox.max = {int32_t(round(xMax / 64.0f)), int32_t(round(yMax / 64.0f))};
} else {
renderRegion.min.x = xMin >> 6;
renderRegion.max.x = (xMax + 63) >> 6;
renderRegion.min.y = yMin >> 6;
renderRegion.max.y = (yMax + 63) >> 6;
renderBox.min = {xMin >> 6, yMin >> 6};
renderBox.max = {(xMax + 63) >> 6, (yMax + 63) >> 6};
}
return mathClipBBox(clipRegion, renderRegion);
renderBox.intersect(clipBox);
return renderBox.valid();
}

View file

@ -61,7 +61,7 @@ static inline int _gaussianRemap(int end, int idx)
//TODO: SIMD OPTIMIZATION?
template<int border = 0>
static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, bool flipped)
static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t w, int32_t h, const RenderRegion& bbox, int32_t dimension, bool flipped)
{
if (flipped) {
src += (bbox.min.x * stride + bbox.min.y) << 2;
@ -136,17 +136,17 @@ static int _gaussianInit(SwGaussianBlur* data, float sigma, int quality)
bool effectGaussianBlurRegion(RenderEffectGaussianBlur* params)
{
//bbox region expansion for feathering
auto& region = params->extend;
//region expansion for feathering
auto& bbox = params->extend;
auto extra = static_cast<SwGaussianBlur*>(params->rd)->extends;
if (params->direction != 2) {
region.x = -extra;
region.w = extra * 2;
bbox.min.x = -extra;
bbox.max.x = extra;
}
if (params->direction != 1) {
region.y = -extra;
region.h = extra * 2;
bbox.min.y = -extra;
bbox.max.y = extra;
}
return true;
@ -230,7 +230,7 @@ struct SwDropShadow : SwGaussianBlur
//TODO: SIMD OPTIMIZATION?
static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, int h, const SwBBox& bbox, int32_t dimension, uint32_t color, bool flipped)
static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, int h, const RenderRegion& bbox, int32_t dimension, uint32_t color, bool flipped)
{
if (flipped) {
src += (bbox.min.x * stride + bbox.min.y);
@ -267,20 +267,20 @@ static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, i
}
static void _dropShadowShift(uint32_t* dst, uint32_t* src, int dstride, int sstride, SwBBox& region, SwPoint& offset, uint8_t opacity, bool direct)
static void _dropShadowShift(uint32_t* dst, uint32_t* src, int dstride, int sstride, RenderRegion& bbox, SwPoint& offset, uint8_t opacity, bool direct)
{
src += (region.min.y * sstride + region.min.x);
dst += (region.min.y * dstride + region.min.x);
src += (bbox.min.y * sstride + bbox.min.x);
dst += (bbox.min.y * dstride + bbox.min.x);
auto w = region.max.x - region.min.x;
auto h = region.max.y - region.min.y;
auto w = bbox.max.x - bbox.min.x;
auto h = bbox.max.y - bbox.min.y;
auto translucent = (direct || opacity < 255);
//shift offset
if (region.min.x + offset.x < 0) src -= offset.x;
if (bbox.min.x + offset.x < 0) src -= offset.x;
else dst += offset.x;
if (region.min.y + offset.y < 0) src -= (offset.y * sstride);
if (bbox.min.y + offset.y < 0) src -= (offset.y * sstride);
else dst += (offset.y * dstride);
for (auto y = 0; y < h; ++y) {
@ -294,20 +294,19 @@ static void _dropShadowShift(uint32_t* dst, uint32_t* src, int dstride, int sstr
bool effectDropShadowRegion(RenderEffectDropShadow* params)
{
//bbox region expansion for feathering
auto& region = params->extend;
//region expansion for feathering
auto& bbox = params->extend;
auto& offset = static_cast<SwDropShadow*>(params->rd)->offset;
auto extra = static_cast<SwDropShadow*>(params->rd)->extends;
region.x = -extra;
region.w = extra * 2;
region.y = -extra;
region.h = extra * 2;
bbox.min = {-extra, -extra};
bbox.max = {extra, extra};
region.x = std::min(region.x + (int32_t)offset.x, region.x);
region.y = std::min(region.y + (int32_t)offset.y, region.y);
region.w += abs(offset.x);
region.h += abs(offset.y);
if (offset.x < 0) bbox.min.x += (int32_t) offset.x;
else bbox.max.x += offset.x;
if (offset.y < 0) bbox.min.y += (int32_t) offset.y;
else bbox.max.y += offset.y;
return true;
}

File diff suppressed because it is too large Load diff

View file

@ -99,15 +99,15 @@ static void avxRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32
}
static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, const RenderColor& c)
static bool avxRasterTranslucentRect(SwSurface* surface, const RenderRegion& bbox, const RenderColor& c)
{
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
auto h = bbox.h();
auto w = bbox.w();
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(c.r, c.g, c.b, c.a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto buffer = surface->buf32 + (bbox.min.y * surface->stride) + bbox.min.x;
uint32_t ialpha = 255 - c.a;
@ -145,7 +145,7 @@ static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, c
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto buffer = surface->buf8 + (bbox.min.y * surface->stride) + bbox.min.x;
auto ialpha = ~c.a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
@ -160,14 +160,12 @@ static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, c
static bool avxRasterTranslucentRle(SwSurface* surface, const SwRle* rle, const RenderColor& c)
{
auto span = rle->spans;
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(c.r, c.g, c.b, c.a);
uint32_t src;
for (uint32_t i = 0; i < rle->size; ++i) {
ARRAY_FOREACH(span, rle->spans) {
auto dst = &surface->buf32[span->y * surface->stride + span->x];
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
@ -213,7 +211,7 @@ static bool avxRasterTranslucentRle(SwSurface* surface, const SwRle* rle, const
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
ARRAY_FOREACH(span, rle->spans) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = MULTIPLY(span->coverage, c.a);
else src = c.a;

View file

@ -94,13 +94,11 @@ static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int
static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRle* rle, const RenderColor& c)
{
auto span = rle->spans;
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(c.r, c.g, c.b, c.a);
uint32_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
ARRAY_FOREACH(span, rle->spans) {
auto dst = &surface->buf32[span->y * surface->stride + span->x];
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
@ -112,7 +110,7 @@ static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRle* rle, c
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
ARRAY_FOREACH(span, rle->spans) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = MULTIPLY(span->coverage, c.a);
else src = c.a;
@ -126,29 +124,26 @@ static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRle* rle, c
}
static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& region, const RenderColor& c)
static bool inline cRasterTranslucentRect(SwSurface* surface, const RenderRegion& bbox, const RenderColor& c)
{
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(c.r, c.g, c.b, c.a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto buffer = surface->buf32 + (bbox.min.y * surface->stride) + bbox.min.x;
auto ialpha = 255 - c.a;
for (uint32_t y = 0; y < h; ++y) {
for (uint32_t y = 0; y < bbox.h(); ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
for (uint32_t x = 0; x < bbox.w(); ++x, ++dst) {
*dst = color + ALPHA_BLEND(*dst, ialpha);
}
}
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto buffer = surface->buf8 + (bbox.min.y * surface->stride) + bbox.min.x;
auto ialpha = ~c.a;
for (uint32_t y = 0; y < h; ++y) {
for (uint32_t y = 0; y < bbox.h(); ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
for (uint32_t x = 0; x < bbox.w(); ++x, ++dst) {
*dst = c.a + MULTIPLY(*dst, ialpha);
}
}

View file

@ -91,8 +91,6 @@ static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int3
static bool neonRasterTranslucentRle(SwSurface* surface, const SwRle* rle, const RenderColor& c)
{
auto span = rle->spans;
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(c.r, c.g, c.b, c.a);
@ -100,7 +98,7 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRle* rle, const
uint8x8_t *vDst = nullptr;
uint16_t align;
for (uint32_t i = 0; i < rle->size; ++i) {
ARRAY_FOREACH(span, rle->spans) {
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
@ -132,7 +130,7 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRle* rle, const
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
ARRAY_FOREACH(span, rle->spans) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = MULTIPLY(span->coverage, c.a);
else src = c.a;
@ -146,15 +144,15 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRle* rle, const
}
static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, const RenderColor& c)
static bool neonRasterTranslucentRect(SwSurface* surface, const RenderRegion& bbox, const RenderColor& c)
{
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
auto h = bbox.h();
auto w = bbox.w();
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->join(c.r, c.g, c.b, c.a);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto buffer = surface->buf32 + (bbox.min.y * surface->stride) + bbox.min.x;
auto ialpha = 255 - c.a;
auto vColor = vdup_n_u32(color);
@ -185,7 +183,7 @@ static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region,
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto buffer = surface->buf8 + (bbox.min.y * surface->stride) + bbox.min.x;
auto ialpha = ~c.a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];

View file

@ -51,100 +51,57 @@ static float dxdya, dxdyb, dudya, dvdya;
static float xa, xb, ua, va;
//Y Range exception handling
static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, int& yEnd)
static inline int32_t _modf(float v)
{
int32_t regionTop, regionBottom;
if (region) {
regionTop = region->min.y;
regionBottom = region->max.y;
} else {
regionTop = image->rle->spans->y;
regionBottom = image->rle->spans[image->rle->size - 1].y;
}
if (yStart < regionTop) yStart = regionTop;
if (yEnd > regionBottom) yEnd = regionBottom;
return yEnd > yStart;
return 255 - ((int(v * 256.0f)) & 255);
}
static bool _rasterMaskedPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag = 0)
static bool _rasterMaskedPolygonImageSegment(SwSurface* surface, const SwImage& image, const RenderRegion& bbox, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag = 0)
{
TVGERR("SW_ENGINE", "TODO: _rasterMaskedPolygonImageSegment()");
return false;
}
static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity)
static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage& image, const RenderRegion& bbox, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity)
{
float _dudx = dudx, _dvdx = dvdx;
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
float _xa = xa, _xb = xb, _ua = ua, _va = va;
auto sbuf = image->buf32;
auto sbuf = image.buf32;
auto dbuf = surface->buf32;
int32_t sw = static_cast<int32_t>(image->w);
int32_t sh = static_cast<int32_t>(image->h);
int32_t sw = static_cast<int32_t>(image.w);
int32_t sh = static_cast<int32_t>(image.h);
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
int32_t vv = 0, uu = 0;
int32_t minx = INT32_MAX, maxx = 0;
float dx, u, v, iptr;
float dx, u, v;
uint32_t* buf;
SwSpan* span = nullptr; //used only when rle based.
if (!_arrange(image, region, yStart, yEnd)) return;
//Loop through all lines in the segment
uint32_t spanIdx = 0;
if (region) {
minx = region->min.x;
maxx = region->max.x;
} else {
span = image->rle->spans;
while (span->y < yStart) {
++span;
++spanIdx;
}
}
if (yStart < bbox.min.y) yStart = bbox.min.y;
if (yEnd > bbox.max.y) yEnd = bbox.max.y;
y = yStart;
while (y < yEnd) {
x1 = (int32_t)_xa;
x2 = (int32_t)_xb;
if (!region) {
minx = INT32_MAX;
maxx = 0;
//one single row, could be consisted of multiple spans.
while (span->y == y && spanIdx < image->rle->size) {
if (minx > span->x) minx = span->x;
if (maxx < span->x + span->len) maxx = span->x + span->len;
++span;
++spanIdx;
}
}
if (x1 < minx) x1 = minx;
if (x2 > maxx) x2 = maxx;
x1 = std::max((int32_t)_xa, bbox.min.x);
x2 = std::min((int32_t)_xb, bbox.max.x);
//Anti-Aliasing frames
if (aaSpans) {
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
}
//Range allowed
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
if ((x2 - x1) >= 1 && (x1 < bbox.max.x) && (x2 > bbox.min.x)) {
//Perform subtexel pre-stepping on UV
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
v = _va + dx * _dvdx;
buf = dbuf + ((y * surface->stride) + x1);
x = x1;
//Draw horizontal line
@ -152,29 +109,29 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage
uu = (int) u;
vv = (int) v;
if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue;
if ((uint32_t) uu >= image.w || (uint32_t) vv >= image.h) continue;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
ar = _modf(u);
ab = _modf(v);
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * image->stride) + uu);
px = *(sbuf + (vv * image.stride) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * image->stride) + iru);
int px2 = *(sbuf + (vv * image.stride) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * image->stride) + uu);
int px2 = *(sbuf + (irv * image.stride) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * image->stride) + iru);
int px3 = *(sbuf + (irv * image.stride) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
@ -195,8 +152,6 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage
_ua += _dudya;
_va += _dvdya;
if (!region && spanIdx >= image->rle->size) break;
++y;
}
xa = _xa;
@ -206,122 +161,95 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage
}
static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, bool matting)
static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage& image, const RenderRegion& bbox, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, bool matting)
{
float _dudx = dudx, _dvdx = dvdx;
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
float _xa = xa, _xb = xb, _ua = ua, _va = va;
auto sbuf = image->buf32;
auto sbuf = image.buf32;
auto dbuf = surface->buf32;
int32_t sw = static_cast<int32_t>(image->w);
int32_t sh = static_cast<int32_t>(image->h);
int32_t sw = static_cast<int32_t>(image.w);
int32_t sh = static_cast<int32_t>(image.h);
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
int32_t vv = 0, uu = 0;
int32_t minx = INT32_MAX, maxx = 0;
float dx, u, v, iptr;
float dx, u, v;
uint32_t* buf;
SwSpan* span = nullptr; //used only when rle based.
//for matting(composition)
auto csize = matting ? surface->compositor->image.channelSize: 0;
auto alpha = matting ? surface->alpha(surface->compositor->method) : nullptr;
uint8_t* cmp = nullptr;
if (!_arrange(image, region, yStart, yEnd)) return;
//Loop through all lines in the segment
uint32_t spanIdx = 0;
if (region) {
minx = region->min.x;
maxx = region->max.x;
} else {
span = image->rle->spans;
while (span->y < yStart) {
++span;
++spanIdx;
}
}
if (yStart < bbox.min.y) yStart = bbox.min.y;
if (yEnd > bbox.max.y) yEnd = bbox.max.y;
y = yStart;
while (y < yEnd) {
x1 = (int32_t)_xa;
x2 = (int32_t)_xb;
if (!region) {
minx = INT32_MAX;
maxx = 0;
//one single row, could be consisted of multiple spans.
while (span->y == y && spanIdx < image->rle->size) {
if (minx > span->x) minx = span->x;
if (maxx < span->x + span->len) maxx = span->x + span->len;
++span;
++spanIdx;
}
}
if (x1 < minx) x1 = minx;
if (x2 > maxx) x2 = maxx;
x1 = std::max((int32_t)_xa, bbox.min.x);
x2 = std::min((int32_t)_xb, bbox.max.x);
//Anti-Aliasing frames
if (aaSpans) {
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
}
//Range allowed
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
if ((x2 - x1) >= 1 && (x1 < bbox.max.x) && (x2 > bbox.min.x)) {
//Perform subtexel pre-stepping on UV
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
v = _va + dx * _dvdx;
buf = dbuf + ((y * surface->stride) + x1);
x = x1;
if (matting) cmp = &surface->compositor->image.buf8[(y * surface->compositor->image.stride + x1) * csize];
if (opacity == 255) {
const auto fullOpacity = (opacity == 255);
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
vv = (int) v;
if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue;
if ((uint32_t) uu >= image.w || (uint32_t) vv >= image.h) continue;
ar = (int)(255.0f * (1.0f - modff(u, &iptr)));
ab = (int)(255.0f * (1.0f - modff(v, &iptr)));
ar = _modf(u);
ab = _modf(v);
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * image->stride) + uu);
px = *(sbuf + (vv * image.stride) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * image->stride) + iru);
int px2 = *(sbuf + (vv * image.stride) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * image->stride) + uu);
int px2 = *(sbuf + (irv * image.stride) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * image->stride) + iru);
int px3 = *(sbuf + (irv * image.stride) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
uint32_t src;
if (matting) {
src = ALPHA_BLEND(px, alpha(cmp));
auto a = alpha(cmp);
src = fullOpacity ? ALPHA_BLEND(px, a) : ALPHA_BLEND(px, MULTIPLY(opacity, a));
cmp += csize;
} else {
src = px;
src = fullOpacity ? px : ALPHA_BLEND(px, opacity);
}
*buf = src + ALPHA_BLEND(*buf, IA(src));
++buf;
@ -330,55 +258,6 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image,
u += _dudx;
v += _dvdx;
}
} else {
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
vv = (int) v;
if ((uint32_t) uu >= image->w || (uint32_t) vv >= image->h) continue;
ar = (int)(255.0f * (1.0f - modff(u, &iptr)));
ab = (int)(255.0f * (1.0f - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * image->stride) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * image->stride) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * image->stride) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
uint32_t src;
if (matting) {
src = ALPHA_BLEND(px, MULTIPLY(opacity, alpha(cmp)));
cmp += csize;
} else {
src = ALPHA_BLEND(px, opacity);
}
*buf = src + ALPHA_BLEND(*buf, IA(src));
++buf;
//Step UV horizontally
u += _dudx;
v += _dvdx;
}
}
}
//Step along both edges
@ -387,8 +266,6 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image,
_ua += _dudya;
_va += _dvdya;
if (!region && spanIdx >= image->rle->size) break;
++y;
}
xa = _xa;
@ -399,7 +276,7 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image,
/* This mapping algorithm is based on Mikael Kalms's. */
static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const SwBBox* region, Polygon& polygon, AASpans* aaSpans, uint8_t opacity)
static void _rasterPolygonImage(SwSurface* surface, const SwImage& image, const RenderRegion& bbox, Polygon& polygon, AASpans* aaSpans, uint8_t opacity)
{
float x[3] = {polygon.vertex[0].pt.x, polygon.vertex[1].pt.x, polygon.vertex[2].pt.x};
float y[3] = {polygon.vertex[0].pt.y, polygon.vertex[1].pt.y, polygon.vertex[2].pt.y};
@ -460,7 +337,6 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
if (tvg::equal(y[0], y[1])) side = x[0] > x[1];
if (tvg::equal(y[1], y[2])) side = x[2] > x[1];
auto regionTop = region ? region->min.y : image->rle->spans->y; //Normal Image or Rle Image?
auto compositing = _compositing(surface); //Composition required
auto blending = _blending(surface); //Blending required
@ -479,7 +355,7 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
//Draw upper segment if possibly visible
if (yi[0] < yi[1]) {
off_y = y[0] < regionTop ? (regionTop - y[0]) : 0;
off_y = y[0] < bbox.min.y ? (bbox.min.y - y[0]) : 0;
xa += (off_y * dxdya);
ua += (off_y * dudya);
va += (off_y * dvdya);
@ -489,18 +365,18 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
xb = x[0] + dy * dxdyb + (off_y * dxdyb);
if (compositing) {
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, 1);
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, bbox, yi[0], yi[1], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, bbox, yi[0], yi[1], aaSpans, opacity, 1);
} else if (blending) {
_rasterBlendingPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity);
_rasterBlendingPolygonImageSegment(surface, image, bbox, yi[0], yi[1], aaSpans, opacity);
} else {
_rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false);
_rasterPolygonImageSegment(surface, image, bbox, yi[0], yi[1], aaSpans, opacity, false);
}
upper = true;
}
//Draw lower segment if possibly visible
if (yi[1] < yi[2]) {
off_y = y[1] < regionTop ? (regionTop - y[1]) : 0;
off_y = y[1] < bbox.min.y ? (bbox.min.y - y[1]) : 0;
if (!upper) {
xa += (off_y * dxdya);
ua += (off_y * dudya);
@ -510,12 +386,12 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
dxdyb = dxdy[2];
xb = x[1] + (1 - (y[1] - yi[1])) * dxdyb + (off_y * dxdyb);
if (compositing) {
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, 2);
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, bbox, yi[1], yi[2], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, bbox, yi[1], yi[2], aaSpans, opacity, 2);
} else if (blending) {
_rasterBlendingPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity);
_rasterBlendingPolygonImageSegment(surface, image, bbox, yi[1], yi[2], aaSpans, opacity);
} else {
_rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false);
_rasterPolygonImageSegment(surface, image, bbox, yi[1], yi[2], aaSpans, opacity, false);
}
}
//Longer edge is on the right side
@ -527,7 +403,7 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
//Draw upper segment if possibly visible
if (yi[0] < yi[1]) {
off_y = y[0] < regionTop ? (regionTop - y[0]) : 0;
off_y = y[0] < bbox.min.y ? (bbox.min.y - y[0]) : 0;
xb += (off_y *dxdyb);
// Set slopes along left edge and perform subpixel pre-stepping
@ -540,18 +416,18 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
va = v[0] + dy * dvdya + (off_y * dvdya);
if (compositing) {
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, 3);
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, bbox, yi[0], yi[1], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, bbox, yi[0], yi[1], aaSpans, opacity, 3);
} else if (blending) {
_rasterBlendingPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity);
_rasterBlendingPolygonImageSegment(surface, image, bbox, yi[0], yi[1], aaSpans, opacity);
} else {
_rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false);
_rasterPolygonImageSegment(surface, image, bbox, yi[0], yi[1], aaSpans, opacity, false);
}
upper = true;
}
//Draw lower segment if possibly visible
if (yi[1] < yi[2]) {
off_y = y[1] < regionTop ? (regionTop - y[1]) : 0;
off_y = y[1] < bbox.min.y ? (bbox.min.y - y[1]) : 0;
if (!upper) xb += (off_y *dxdyb);
// Set slopes along left edge and perform subpixel pre-stepping
@ -564,25 +440,20 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
va = v[1] + dy * dvdya + (off_y * dvdya);
if (compositing) {
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, 4);
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, bbox, yi[1], yi[2], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, bbox, yi[1], yi[2], aaSpans, opacity, 4);
} else if (blending) {
_rasterBlendingPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity);
_rasterBlendingPolygonImageSegment(surface, image, bbox, yi[1], yi[2], aaSpans, opacity);
} else {
_rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false);
_rasterPolygonImageSegment(surface, image, bbox, yi[1], yi[2], aaSpans, opacity, false);
}
}
}
}
static AASpans* _AASpans(float ymin, float ymax, const SwImage* image, const SwBBox* region)
static AASpans* _AASpans(int yStart, int yEnd)
{
auto yStart = static_cast<int>(ymin);
auto yEnd = static_cast<int>(ymax);
if (!_arrange(image, region, yStart, yEnd)) return nullptr;
auto aaSpans = tvg::malloc<AASpans*>(sizeof(AASpans));
aaSpans->yStart = yStart;
aaSpans->yEnd = yEnd;
@ -661,14 +532,9 @@ static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
ptx[1] = tx[1]; \
} while (0)
struct Point
{
int32_t x, y;
};
int32_t y = 0;
Point pEdge = {-1, -1}; //previous edge point
Point edgeDiff = {0, 0}; //temporary used for point distance
SwPoint pEdge = {-1, -1}; //previous edge point
SwPoint edgeDiff = {0, 0}; //temporary used for point distance
/* store bigger to tx[0] between prev and current edge's x positions. */
int32_t tx[2] = {0, 0};
@ -791,29 +657,24 @@ static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
}
static bool _apply(SwSurface* surface, AASpans* aaSpans)
static void _apply(SwSurface* surface, AASpans* aaSpans)
{
auto end = surface->buf32 + surface->h * surface->stride;
auto buf = surface->buf32 + surface->stride * aaSpans->yStart;
auto y = aaSpans->yStart;
uint32_t pixel;
auto line = aaSpans->lines;
uint32_t pix;
uint32_t* dst;
int32_t pos;
//left side
_calcAAEdge(aaSpans, 0);
//right side
_calcAAEdge(aaSpans, 1);
_calcAAEdge(aaSpans, 0); //left side
_calcAAEdge(aaSpans, 1); //right side
while (y < aaSpans->yEnd) {
auto line = &aaSpans->lines[y - aaSpans->yStart];
auto width = line->x[1] - line->x[0];
if (width > 0) {
auto offset = y * surface->stride;
if (line->x[1] - line->x[0] > 0) {
//Left edge
dst = surface->buf32 + (offset + line->x[0]);
if (line->x[0] > 1) pixel = *(dst - 1);
else pixel = *dst;
dst = buf + line->x[0];
pix = *(dst - ((line->x[0] > 1) ? 1 : 0));
pos = 1;
//exceptional handling. out of memory bound.
@ -822,34 +683,31 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
}
while (pos <= line->length[0]) {
*dst = INTERPOLATE(*dst, pixel, line->coverage[0] * pos);
*dst = INTERPOLATE(*dst, pix, line->coverage[0] * pos);
++dst;
++pos;
}
//Right edge
dst = surface->buf32 + offset + line->x[1] - 1;
if (line->x[1] < (int32_t)(surface->w - 1)) pixel = *(dst + 1);
else pixel = *dst;
dst = buf + line->x[1] - 1;
pix = *(dst + (line->x[1] < (int32_t)(surface->w - 1) ? 1 : 0));
pos = line->length[1];
//exceptional handling. out of memory bound.
if (dst - pos < surface->buf32) --pos;
while (pos > 0) {
*dst = INTERPOLATE(*dst, pixel, 255 - (line->coverage[1] * pos));
*dst = INTERPOLATE(*dst, pix, 255 - (line->coverage[1] * pos));
--dst;
--pos;
}
}
y++;
buf += surface->stride;
++line;
++y;
}
tvg::free(aaSpans->lines);
tvg::free(aaSpans);
return true;
}
@ -863,23 +721,19 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
| / |
3 -- 2
*/
static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix& transform, const SwBBox* region, uint8_t opacity)
bool rasterTexmapPolygon(SwSurface* surface, const SwImage& image, const Matrix& transform, const RenderRegion& bbox, uint8_t opacity)
{
if (surface->channelSize == sizeof(uint8_t)) {
TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon!");
return false;
}
//Exceptions: No dedicated drawing area?
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return true;
/* Prepare vertices.
shift XY coordinates to match the sub-pixeling technique. */
//Prepare vertices. Shift XY coordinates to match the sub-pixeling technique.
Vertex vertices[4];
vertices[0] = {{0.0f, 0.0f}, {0.0f, 0.0f}};
vertices[1] = {{float(image->w), 0.0f}, {float(image->w), 0.0f}};
vertices[2] = {{float(image->w), float(image->h)}, {float(image->w), float(image->h)}};
vertices[3] = {{0.0f, float(image->h)}, {0.0f, float(image->h)}};
vertices[1] = {{float(image.w), 0.0f}, {float(image.w), 0.0f}};
vertices[2] = {{float(image.w), float(image.h)}, {float(image.w), float(image.h)}};
vertices[3] = {{0.0f, float(image.h)}, {0.0f, float(image.h)}};
float ys = FLT_MAX, ye = -1.0f;
for (int i = 0; i < 4; i++) {
@ -888,8 +742,9 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
if (vertices[i].pt.y > ye) ye = vertices[i].pt.y;
}
auto aaSpans = _AASpans(ys, ye, image, region);
if (!aaSpans) return true;
auto yStart = std::max(static_cast<int>(ys), bbox.min.y);
auto yEnd = std::min(static_cast<int>(ye), bbox.max.y);
auto aaSpans = rightAngle(transform) ? nullptr : _AASpans(yStart, yEnd);
Polygon polygon;
@ -898,19 +753,20 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
polygon.vertex[1] = vertices[1];
polygon.vertex[2] = vertices[3];
_rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity);
_rasterPolygonImage(surface, image, bbox, polygon, aaSpans, opacity);
//Draw the second polygon
polygon.vertex[0] = vertices[1];
polygon.vertex[1] = vertices[2];
polygon.vertex[2] = vertices[3];
_rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity);
_rasterPolygonImage(surface, image, bbox, polygon, aaSpans, opacity);
#if 0
if (_compositing(surface) && _masking(surface) && !_direct(surface->compositor->method)) {
_compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox);
}
#endif
return _apply(surface, aaSpans);
if (aaSpans) _apply(surface, aaSpans);
return true;
}

View file

@ -40,7 +40,7 @@ struct SwTask : Task
{
SwSurface* surface = nullptr;
SwMpool* mpool = nullptr;
SwBBox bbox; //Rendering Region
RenderRegion bbox; //Rendering Region
Matrix transform;
Array<RenderData> clips;
RenderUpdateFlag flags = RenderUpdateFlag::None;
@ -48,22 +48,11 @@ struct SwTask : Task
bool pushed = false; //Pushed into task list?
bool disposed = false; //Disposed task?
RenderRegion bounds()
const RenderRegion& bounds()
{
//Can we skip the synchronization?
done();
RenderRegion region;
//Range over?
region.x = bbox.min.x > 0 ? bbox.min.x : 0;
region.y = bbox.min.y > 0 ? bbox.min.y : 0;
region.w = bbox.max.x - region.x;
region.h = bbox.max.y - region.y;
if (region.w < 0) region.w = 0;
if (region.h < 0) region.h = 0;
return region;
return bbox;
}
virtual void dispose() = 0;
@ -117,7 +106,7 @@ struct SwShapeTask : SwTask
}
auto strokeWidth = validStrokeWidth(clipper);
SwBBox renderRegion{};
RenderRegion renderBox{};
auto updateShape = flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform | RenderUpdateFlag::Clip);
auto updateFill = false;
@ -126,11 +115,11 @@ struct SwShapeTask : SwTask
updateFill = (MULTIPLY(rshape->color.a, opacity) || rshape->fill);
if (updateShape) shapeReset(&shape);
if (updateFill || clipper) {
if (shapePrepare(&shape, rshape, transform, bbox, renderRegion, mpool, tid, clips.count > 0 ? true : false)) {
if (shapePrepare(&shape, rshape, transform, bbox, renderBox, mpool, tid, clips.count > 0 ? true : false)) {
if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err;
} else {
updateFill = false;
renderRegion.reset();
renderBox.reset();
}
}
}
@ -146,7 +135,7 @@ struct SwShapeTask : SwTask
if (updateShape || flags & RenderUpdateFlag::Stroke) {
if (strokeWidth > 0.0f) {
shapeResetStroke(&shape, rshape, transform);
if (!shapeGenStrokeRle(&shape, rshape, transform, bbox, renderRegion, mpool, tid)) goto err;
if (!shapeGenStrokeRle(&shape, rshape, transform, bbox, renderBox, mpool, tid)) goto err;
if (auto fill = rshape->strokeFill()) {
auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false;
if (ctable) shapeResetStrokeFill(&shape);
@ -168,7 +157,7 @@ struct SwShapeTask : SwTask
if (!clipShapeRle && !clipStrokeRle) goto err;
}
bbox = renderRegion; //sync
bbox = renderBox; //sync
return;
@ -199,7 +188,7 @@ struct SwImageTask : SwTask
void run(unsigned tid) override
{
auto clipRegion = bbox;
auto clipBox = bbox;
//Convert colorspace if it's not aligned.
rasterConvertCS(source, surface->cs);
@ -216,7 +205,7 @@ struct SwImageTask : SwTask
imageReset(&image);
if (!image.data || image.w == 0 || image.h == 0) goto end;
if (!imagePrepare(&image, transform, clipRegion, bbox, mpool, tid)) goto end;
if (!imagePrepare(&image, transform, clipBox, bbox, mpool, tid)) goto end;
if (clips.count > 0) {
if (!imageGenRle(&image, bbox, false)) goto end;
@ -245,26 +234,26 @@ struct SwImageTask : SwTask
};
static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
static void _renderFill(SwShapeTask* task, SwSurface* surface)
{
if (auto fill = task->rshape->fill) {
rasterGradientShape(surface, &task->shape, fill, opacity);
rasterGradientShape(surface, &task->shape, fill, task->opacity);
} else {
RenderColor c;
task->rshape->fillColor(&c.r, &c.g, &c.b, &c.a);
c.a = MULTIPLY(opacity, c.a);
c.a = MULTIPLY(task->opacity, c.a);
if (c.a > 0) rasterShape(surface, &task->shape, c);
}
}
static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
static void _renderStroke(SwShapeTask* task, SwSurface* surface)
{
if (auto strokeFill = task->rshape->strokeFill()) {
rasterGradientStroke(surface, &task->shape, strokeFill, opacity);
rasterGradientStroke(surface, &task->shape, strokeFill, task->opacity);
} else {
RenderColor c;
if (task->rshape->strokeFill(&c.r, &c.g, &c.b, &c.a)) {
c.a = MULTIPLY(opacity, c.a);
c.a = MULTIPLY(task->opacity, c.a);
if (c.a > 0) rasterStroke(surface, &task->shape, c);
}
}
@ -359,7 +348,7 @@ bool SwRenderer::target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h,
bool SwRenderer::preUpdate()
{
return true;
return surface != nullptr;
}
@ -371,7 +360,7 @@ bool SwRenderer::postUpdate()
bool SwRenderer::preRender()
{
return true;
return surface != nullptr;
}
@ -394,12 +383,6 @@ bool SwRenderer::postRender()
rasterUnpremultiply(surface);
}
ARRAY_FOREACH(p, tasks) {
if ((*p)->disposed) delete(*p);
else (*p)->pushed = false;
}
tasks.clear();
return true;
}
@ -411,7 +394,32 @@ bool SwRenderer::renderImage(RenderData data)
if (task->opacity == 0) return true;
return rasterImage(surface, &task->image, task->transform, task->bbox, task->opacity);
//Outside of the viewport, skip the rendering
auto& bbox = task->bbox;
if (bbox.invalid() || bbox.x() >= surface->w || bbox.y() >= surface->h) return true;
auto& image = task->image;
//RLE Image
if (image.rle) {
if (image.direct) return rasterDirectRleImage(surface, image, task->opacity);
else if (image.scaled) return rasterScaledRleImage(surface, image, task->transform, bbox, task->opacity);
else {
//create a intermediate buffer for rle clipping
auto cmp = request(sizeof(pixel_t), false);
cmp->compositor->method = MaskMethod::None;
cmp->compositor->valid = true;
cmp->compositor->image.rle = image.rle;
rasterClear(cmp, bbox.x(), bbox.y(), bbox.w(), bbox.h(), 0);
rasterTexmapPolygon(cmp, image, task->transform, bbox, 255);
return rasterDirectRleImage(surface, cmp->compositor->image, task->opacity);
}
//Whole Image
} else {
if (image.direct) return rasterDirectImage(surface, image, bbox, task->opacity);
else if (image.scaled) return rasterScaledImage(surface, image, task->transform, bbox, task->opacity);
else return rasterTexmapPolygon(surface, image, task->transform, bbox, task->opacity);
}
}
@ -426,11 +434,11 @@ bool SwRenderer::renderShape(RenderData data)
//Main raster stage
if (task->rshape->strokeFirst()) {
_renderStroke(task, surface, task->opacity);
_renderFill(task, surface, task->opacity);
_renderStroke(task, surface);
_renderFill(task, surface);
} else {
_renderFill(task, surface, task->opacity);
_renderStroke(task, surface, task->opacity);
_renderFill(task, surface);
_renderStroke(task, surface);
}
return true;
@ -570,38 +578,19 @@ SwSurface* SwRenderer::request(int channelSize, bool square)
RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags)
{
auto x = region.x;
auto y = region.y;
auto w = region.w;
auto h = region.h;
auto sw = static_cast<int32_t>(surface->w);
auto sh = static_cast<int32_t>(surface->h);
//Out of boundary
if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr;
auto bbox = RenderRegion::intersect(region, {{0, 0}, {int32_t(surface->w), int32_t(surface->h)}});
if (bbox.invalid()) return nullptr;
auto cmp = request(CHANNEL_SIZE(cs), (flags & CompositionFlag::PostProcessing));
//Boundary Check
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x + w > sw) w = (sw - x);
if (y + h > sh) h = (sh - y);
if (w == 0 || h == 0) return nullptr;
cmp->compositor->recoverSfc = surface;
cmp->compositor->recoverCmp = surface->compositor;
cmp->compositor->valid = false;
cmp->compositor->bbox.min.x = x;
cmp->compositor->bbox.min.y = y;
cmp->compositor->bbox.max.x = x + w;
cmp->compositor->bbox.max.y = y + h;
cmp->compositor->bbox = bbox;
/* TODO: Currently, only blending might work.
Blending and composition must be handled together. */
auto color = (surface->blender && !surface->compositor) ? 0x00ffffff : 0x00000000;
rasterClear(cmp, x, y, w, h, color);
rasterClear(cmp, bbox.x(), bbox.y(), bbox.w(), bbox.h(), color);
//Switch render target
surface = cmp;
@ -626,8 +615,7 @@ bool SwRenderer::endComposite(RenderCompositor* cmp)
//Default is alpha blending
if (p->method == MaskMethod::None) {
auto m = tvg::identity();
return rasterImage(surface, &p->image, m, p->bbox, p->opacity);
return rasterDirectImage(surface, p->image, p->bbox, p->opacity);
}
return true;
@ -722,6 +710,20 @@ void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Arr
{
if (!surface) return task;
if (flags == RenderUpdateFlag::None) return task;
if ((transform.e11 == 0.0f && transform.e12 == 0.0f) || (transform.e21 == 0.0f && transform.e22 == 0.0f)) return task; //zero size?
task->surface = surface;
task->mpool = mpool;
task->bbox = RenderRegion::intersect(vport, {{0, 0}, {int32_t(surface->w), int32_t(surface->h)}});
task->transform = transform;
task->clips = clips;
task->opacity = opacity;
task->flags = flags;
if (!task->pushed) {
task->pushed = true;
tasks.push(task);
}
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
@ -730,27 +732,6 @@ void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Arr
static_cast<SwTask*>(*p)->done();
}
task->clips = clips;
task->transform = transform;
//zero size?
if (task->transform.e11 == 0.0f && task->transform.e12 == 0.0f) return task; //zero width
if (task->transform.e21 == 0.0f && task->transform.e22 == 0.0f) return task; //zero height
task->opacity = opacity;
task->surface = surface;
task->mpool = mpool;
task->flags = flags;
task->bbox.min.x = std::max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x));
task->bbox.min.y = std::max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y));
task->bbox.max.x = std::min(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w));
task->bbox.max.y = std::min(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h));
if (!task->pushed) {
task->pushed = true;
tasks.push(task);
}
TaskScheduler::request(task);
return task;

View file

@ -56,6 +56,8 @@ public:
bool sync() override;
bool target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs);
SwSurface* request(int channelSize, bool square);
RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) override;
bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) override;
bool endComposite(RenderCompositor* cmp) override;
@ -80,7 +82,6 @@ private:
SwRenderer();
~SwRenderer();
SwSurface* request(int channelSize, bool square);
RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
};

View file

@ -324,27 +324,18 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor
if (!rw.antiAlias) coverage = 255;
//see whether we can add this span to the current list
if (rle->size > 0) {
auto span = rle->spans + rle->size - 1;
if ((span->coverage == coverage) && (span->y == y) && (span->x + span->len == x)) {
if (!rle->spans.empty()) {
auto& span = rle->spans.last();
if ((span.coverage == coverage) && (span.y == y) && (span.x + span.len == x)) {
//Clip x range
SwCoord xOver = 0;
if (x + aCount >= rw.cellMax.x) xOver -= (x + aCount - rw.cellMax.x);
if (x < rw.cellMin.x) xOver -= (rw.cellMin.x - x);
span->len += (aCount + xOver);
span.len += (aCount + xOver);
return;
}
}
//span pool is full, grow it.
if (rle->size >= rle->alloc) {
auto newSize = (rle->size > 0) ? (rle->size * 2) : 256;
if (rle->alloc < newSize) {
rle->alloc = newSize;
rle->spans = tvg::realloc<SwSpan*>(rle->spans, rle->alloc * sizeof(SwSpan));
}
}
//Clip x range
SwCoord xOver = 0;
if (x + aCount >= rw.cellMax.x) xOver -= (x + aCount - rw.cellMax.x);
@ -357,12 +348,7 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor
if (aCount + xOver <= 0) return;
//add a span to the current list
auto span = rle->spans + rle->size;
span->x = x;
span->y = y;
span->len = (aCount + xOver);
span->coverage = coverage;
rle->size++;
rle->spans.next() = {(uint16_t)x, (uint16_t)y, uint16_t(aCount + xOver), (uint8_t)coverage};
}
@ -737,110 +723,11 @@ static bool _genRle(RleWorker& rw)
}
static SwSpan* _intersectSpansRegion(const SwRle *clip, const SwRle *target, SwSpan *outSpans, uint32_t outSpansCnt)
{
auto out = outSpans;
auto spans = target->spans;
auto end = target->spans + target->size;
auto clipSpans = clip->spans;
auto clipEnd = clip->spans + clip->size;
while (spans < end && clipSpans < clipEnd) {
//align y-coordinates.
if (clipSpans->y > spans->y) {
++spans;
continue;
}
if (spans->y > clipSpans->y) {
++clipSpans;
continue;
}
//Try clipping with all clip spans which have a same y-coordinate.
auto temp = clipSpans;
while(temp < clipEnd && outSpansCnt > 0 && temp->y == clipSpans->y) {
auto sx1 = spans->x;
auto sx2 = sx1 + spans->len;
auto cx1 = temp->x;
auto cx2 = cx1 + temp->len;
//The span must be left(x1) to right(x2) direction. Not intersected.
if (cx2 < sx1 || sx2 < cx1) {
++temp;
continue;
}
//clip span region.
auto x = sx1 > cx1 ? sx1 : cx1;
auto len = (sx2 < cx2 ? sx2 : cx2) - x;
if (len > 0) {
out->x = x;
out->y = temp->y;
out->len = len;
out->coverage = (uint8_t)(((spans->coverage * temp->coverage) + 0xff) >> 8);
++out;
--outSpansCnt;
}
++temp;
}
++spans;
}
return out;
}
static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRle *targetRle, SwSpan *outSpans, uint32_t outSpansCnt)
{
auto out = outSpans;
auto spans = targetRle->spans;
auto end = targetRle->spans + targetRle->size;
auto minx = static_cast<int16_t>(bbox->min.x);
auto miny = static_cast<int16_t>(bbox->min.y);
auto maxx = minx + static_cast<int16_t>(bbox->max.x - bbox->min.x) - 1;
auto maxy = miny + static_cast<int16_t>(bbox->max.y - bbox->min.y) - 1;
while (outSpansCnt > 0 && spans < end) {
if (spans->y > maxy) {
spans = end;
break;
}
if (spans->y < miny || spans->x > maxx || spans->x + spans->len <= minx) {
++spans;
continue;
}
if (spans->x < minx) {
out->len = (spans->len - (minx - spans->x)) < (maxx - minx + 1) ? (spans->len - (minx - spans->x)) : (maxx - minx + 1);
out->x = minx;
}
else {
out->x = spans->x;
out->len = spans->len < (maxx - spans->x + 1) ? spans->len : (maxx - spans->x + 1);
}
if (out->len > 0) {
out->y = spans->y;
out->coverage = spans->coverage;
++out;
--outSpansCnt;
}
++spans;
}
return out;
}
void _replaceClipSpan(SwRle *rle, SwSpan* clippedSpans, uint32_t size)
{
tvg::free(rle->spans);
rle->spans = clippedSpans;
rle->size = rle->alloc = size;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias)
SwRle* rleRender(SwRle* rle, const SwOutline* outline, const RenderRegion& bbox, bool antiAlias)
{
if (!outline) return nullptr;
@ -861,8 +748,8 @@ SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegio
rw.area = 0;
rw.cover = 0;
rw.invalid = true;
rw.cellMin = renderRegion.min;
rw.cellMax = renderRegion.max;
rw.cellMin = {bbox.min.x, bbox.min.y};
rw.cellMax = {bbox.max.x, bbox.max.y};
rw.cellXCnt = rw.cellMax.x - rw.cellMin.x;
rw.cellYCnt = rw.cellMax.y - rw.cellMin.y;
rw.outline = const_cast<SwOutline*>(outline);
@ -870,8 +757,9 @@ SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegio
rw.bandShoot = 0;
rw.antiAlias = antiAlias;
if (!rle) rw.rle = tvg::calloc<SwRle*>(1, sizeof(SwRle));
if (!rle) rw.rle = new SwRle;
else rw.rle = rle;
rw.rle->spans.reserve(256);
//Generate RLE
Band bands[BAND_SIZE];
@ -934,7 +822,10 @@ SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegio
/* This is too complex for a single scanline; there must
be some problems */
if (middle == bottom) goto error;
if (middle == bottom) {
rleFree(rw.rle);
return nullptr;
}
if (bottom - top >= rw.bandSize) ++rw.bandShoot;
@ -945,34 +836,26 @@ SwRle* rleRender(SwRle* rle, const SwOutline* outline, const SwBBox& renderRegio
++band;
}
}
if (rw.bandShoot > 8 && rw.bandSize > 16)
if (rw.bandShoot > 8 && rw.bandSize > 16) {
rw.bandSize = (rw.bandSize >> 1);
}
return rw.rle;
error:
tvg::free(rw.rle);
return nullptr;
}
SwRle* rleRender(const SwBBox* bbox)
SwRle* rleRender(const RenderRegion* bbox)
{
auto width = static_cast<uint16_t>(bbox->max.x - bbox->min.x);
auto height = static_cast<uint16_t>(bbox->max.y - bbox->min.y);
auto rle = tvg::calloc<SwRle*>(sizeof(SwRle), 1);
rle->spans.reserve(bbox->h());
rle->spans.count = bbox->h();
auto rle = tvg::malloc<SwRle*>(sizeof(SwRle));
rle->spans = tvg::malloc<SwSpan*>(sizeof(SwSpan) * height);
rle->size = height;
rle->alloc = height;
//cheaper without push()
auto x = uint16_t(bbox->min.x);
auto y = uint16_t(bbox->min.y);
auto len = uint16_t(bbox->w());
auto span = rle->spans;
for (uint16_t i = 0; i < height; ++i, ++span) {
span->x = bbox->min.x;
span->y = bbox->min.y + i;
span->len = width;
span->coverage = 255;
ARRAY_FOREACH(p, rle->spans) {
*p = {x, y++, len, 255};
}
return rle;
@ -981,44 +864,88 @@ SwRle* rleRender(const SwBBox* bbox)
void rleReset(SwRle* rle)
{
if (!rle) return;
rle->size = 0;
if (rle) rle->spans.clear();
}
void rleFree(SwRle* rle)
{
if (!rle) return;
if (rle->spans) tvg::free(rle->spans);
tvg::free(rle);
delete(rle);
}
bool rleClip(SwRle *rle, const SwRle *clip)
{
if (rle->size == 0 || clip->size == 0) return false;
auto spanCnt = rle->size > clip->size ? rle->size : clip->size;
auto spans = tvg::malloc<SwSpan*>(sizeof(SwSpan) * (spanCnt));
auto spansEnd = _intersectSpansRegion(clip, rle, spans, spanCnt);
if (rle->spans.empty() || clip->spans.empty()) return false;
_replaceClipSpan(rle, spans, spansEnd - spans);
Array<SwSpan> out;
out.reserve(std::max(rle->spans.count, clip->spans.count));
TVGLOG("SW_ENGINE", "Using Path Clipping!");
auto spans = rle->data();
auto end = rle->spans.end();
auto cspans = clip->data();
auto cend = clip->spans.end();
while(spans < end && cspans < cend) {
//align y-coordinates.
if (cspans->y > spans->y) {
++spans;
continue;
}
if (spans->y > cspans->y) {
++cspans;
continue;
}
//try clipping with all clip spans which have a same y-coordinate.
auto temp = cspans;
while(temp < cend && temp->y == cspans->y) {
//span must be left(x1) to right(x2) direction. Not intersected.
if ((spans->x + spans->len) < spans->x || (temp->x + temp->len) < temp->x) {
++temp;
continue;
}
//clip span region
auto x = std::max(spans->x, temp->x);
auto len = std::min((spans->x + spans->len), (temp->x + temp->len)) - x;
if (len > 0) out.next() = {uint16_t(x), temp->y, uint16_t(len), (uint8_t)(((spans->coverage * temp->coverage) + 0xff) >> 8)};
++temp;
}
++spans;
}
out.move(rle->spans);
return true;
}
bool rleClip(SwRle *rle, const SwBBox* clip)
//Need to confirm: dead code?
bool rleClip(SwRle *rle, const RenderRegion* clip)
{
if (rle->size == 0) return false;
auto spans = tvg::malloc<SwSpan*>(sizeof(SwSpan) * (rle->size));
auto spansEnd = _intersectSpansRect(clip, rle, spans, rle->size);
if (rle->spans.empty() || clip->invalid()) return false;
_replaceClipSpan(rle, spans, spansEnd - spans);
auto& min = clip->min;
auto& max = clip->max;
TVGLOG("SW_ENGINE", "Using Box Clipping!");
Array<SwSpan> out;
out.reserve(rle->spans.count);
auto data = out.data;
uint16_t x, len;
ARRAY_FOREACH(p, rle->spans) {
if (p->y >= max.y) break;
if (p->y < min.y || p->x >= max.x || (p->x + p->len) <= min.x) continue;
if (p->x < min.x) {
x = min.x;
len = std::min((p->len - (x - p->x)), (max.x - x));
} else {
x = p->x;
len = std::min(p->len, uint16_t(max.x - x));
}
if (len > 0) {
*data = {x, p->y, len, p->coverage};
++data;
++out.count;
}
}
out.move(rle->spans);
return true;
}

View file

@ -419,13 +419,13 @@ static SwOutline* _genOutline(SwShape* shape, const RenderShape* rshape, const M
/* External Class Implementation */
/************************************************************************/
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite)
bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const RenderRegion& clipBox, RenderRegion& renderBox, SwMpool* mpool, unsigned tid, bool hasComposite)
{
if (auto out = _genOutline(shape, rshape, transform, mpool, tid, hasComposite, rshape->trimpath())) shape->outline = out;
else return false;
if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false;
if (!mathUpdateOutlineBBox(shape->outline, clipBox, renderBox, shape->fastTrack)) return false;
shape->bbox = renderRegion;
shape->bbox = renderBox;
return true;
}
@ -500,7 +500,7 @@ void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix& t
}
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const RenderRegion& clipBox, RenderRegion& renderBox, SwMpool* mpool, unsigned tid)
{
SwOutline* shapeOutline = nullptr;
SwOutline* strokeOutline = nullptr;
@ -528,12 +528,12 @@ bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix&
strokeOutline = strokeExportOutline(shape->stroke, mpool, tid);
if (!mathUpdateOutlineBBox(strokeOutline, clipRegion, renderRegion, false)) {
if (!mathUpdateOutlineBBox(strokeOutline, clipBox, renderBox, false)) {
ret = false;
goto clear;
}
shape->strokeRle = rleRender(shape->strokeRle, strokeOutline, renderRegion, true);
shape->strokeRle = rleRender(shape->strokeRle, strokeOutline, renderBox, true);
clear:
if (dashStroking) mpoolRetDashOutline(mpool, tid);

View file

@ -31,7 +31,7 @@ struct Canvas::Impl
{
Scene* scene;
RenderMethod* renderer;
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
RenderRegion vport = {{0, 0}, {INT32_MAX, INT32_MAX}};
Status status = Status::Synced;
Impl() : scene(Scene::gen())
@ -119,11 +119,11 @@ struct Canvas::Impl
{
if (status != Status::Damaged && status != Status::Synced) return Result::InsufficientCondition;
RenderRegion val = {x, y, w, h};
RenderRegion val = {{x, y}, {x + w, y + h}};
//intersect if the target buffer is already set.
auto surface = renderer->mainSurface();
if (surface && surface->w > 0 && surface->h > 0) {
val.intersect({0, 0, (int32_t)surface->w, (int32_t)surface->h});
val.intersect({{0, 0}, {(int32_t)surface->w, (int32_t)surface->h}});
}
if (vport == val) return Result::Success;
renderer->viewport(val);

View file

@ -51,7 +51,7 @@ Result GlCanvas::target(void* context, int32_t id, uint32_t w, uint32_t h, Color
if (!renderer) return Result::MemoryCorruption;
if (!renderer->target(context, id, w, h)) return Result::Unknown;
pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
pImpl->vport = {{0, 0}, {(int32_t)w, (int32_t)h}};
renderer->viewport(pImpl->vport);
//Paints must be updated again with this new target.

View file

@ -58,7 +58,7 @@ static Result _clipRect(RenderMethod* renderer, const Point* pts, const Matrix&
if (tmp[i].y > max.y) max.y = tmp[i].y;
}
float region[4] = {float(before.x), float(before.x + before.w), float(before.y), float(before.y + before.h)};
float region[4] = {float(before.min.x), float(before.max.x), float(before.min.y), float(before.max.y)};
//figure out if the clipper is a superset of the current viewport(before) region
if (min.x <= region[0] && max.x >= region[1] && min.y <= region[2] && max.y >= region[3]) {
@ -66,7 +66,7 @@ static Result _clipRect(RenderMethod* renderer, const Point* pts, const Matrix&
return Result::Success;
//figure out if the clipper is totally outside of the viewport
} else if (max.x <= region[0] || min.x >= region[1] || max.y <= region[2] || min.y >= region[3]) {
renderer->viewport({0, 0, 0, 0});
renderer->viewport({});
return Result::Success;
}
return Result::InsufficientCondition;
@ -122,13 +122,13 @@ static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Mat
if (v1.x > v2.x) std::swap(v1.x, v2.x);
if (v1.y > v2.y) std::swap(v1.y, v2.y);
after.x = static_cast<int32_t>(nearbyint(v1.x));
after.y = static_cast<int32_t>(nearbyint(v1.y));
after.w = static_cast<int32_t>(nearbyint(v2.x)) - after.x;
after.h = static_cast<int32_t>(nearbyint(v2.y)) - after.y;
after.min.x = static_cast<int32_t>(nearbyint(v1.x));
after.min.y = static_cast<int32_t>(nearbyint(v1.y));
after.max.x = static_cast<int32_t>(nearbyint(v2.x));
after.max.y = static_cast<int32_t>(nearbyint(v2.y));
if (after.w < 0) after.w = 0;
if (after.h < 0) after.h = 0;
if (after.max.x < after.min.x) after.max.x = after.min.x;
if (after.max.y < after.min.y) after.max.y = after.min.y;
after.intersect(before);
renderer->viewport(after);
@ -168,7 +168,7 @@ Paint* Paint::Impl::duplicate(Paint* ret)
ret->pImpl->opacity = opacity;
if (maskData) ret->mask(maskData->target->duplicate(), maskData->method);
if (clipper) ret->clip(clipper->duplicate());
if (clipper) ret->clip(static_cast<Shape*>(clipper->duplicate()));
return ret;
}
@ -185,7 +185,7 @@ bool Paint::Impl::render(RenderMethod* renderer)
PAINT_METHOD(region, bounds(renderer));
if (MASK_REGION_MERGING(maskData->method)) region.add(PAINT(maskData->target)->bounds(renderer));
if (region.w == 0 || region.h == 0) return true;
if (region.invalid()) return true;
cmp = renderer->target(region, MASK_TO_COLORSPACE(renderer, maskData->method), CompositionFlag::Masking);
if (renderer->beginComposite(cmp, MaskMethod::None, 255)) {
maskData->target->pImpl->render(renderer);
@ -376,16 +376,18 @@ Paint* Paint::duplicate() const noexcept
}
Result Paint::clip(Paint* clipper) noexcept
Result Paint::clip(Shape* clipper) noexcept
{
if (clipper && clipper->type() != Type::Shape) {
TVGERR("RENDERER", "Clipping only supports the Shape!");
return Result::NonSupport;
}
return pImpl->clip(clipper);
}
Shape* Paint::clip() const noexcept
{
return pImpl->clipper;
}
Result Paint::mask(Paint* target, MaskMethod method) noexcept
{
return pImpl->mask(target, method);

View file

@ -54,7 +54,7 @@ namespace tvg
Paint* paint = nullptr;
Paint* parent = nullptr;
Mask* maskData = nullptr;
Paint* clipper = nullptr;
Shape* clipper = nullptr;
RenderMethod* renderer = nullptr;
RenderData rd = nullptr;
@ -80,9 +80,9 @@ namespace tvg
} tr;
RenderUpdateFlag renderFlag = RenderUpdateFlag::None;
BlendMethod blendMethod;
uint16_t refCnt = 0; //reference count
uint8_t ctxFlag;
uint8_t opacity;
uint8_t refCnt = 0; //reference count
Impl(Paint* pnt) : paint(pnt)
{
@ -107,9 +107,7 @@ namespace tvg
uint8_t ref()
{
if (refCnt == UINT8_MAX) TVGERR("RENDERER", "Reference Count Overflow!");
else ++refCnt;
return refCnt;
return ++refCnt;
}
uint8_t unref(bool free = true)
@ -163,7 +161,7 @@ namespace tvg
return tm;
}
Result clip(Paint* clp)
Result clip(Shape* clp)
{
if (clp && PAINT(clp)->parent) return Result::InsufficientCondition;
if (clipper) PAINT(clipper)->unref(clipper != clp);

View file

@ -253,7 +253,7 @@ struct PictureImpl : Picture
bool render(RenderMethod* renderer)
{
bool ret = false;
auto ret = true;
renderer->blend(impl.blendMethod);
if (bitmap) return renderer->renderImage(impl.rd);
@ -273,7 +273,7 @@ struct PictureImpl : Picture
{
if (impl.rd) return renderer->region(impl.rd);
if (vector) return vector->pImpl->bounds(renderer);
return {0, 0, 0, 0};
return {};
}
Result load(ImageLoader* loader)

View file

@ -106,33 +106,14 @@ bool RenderPath::bounds(Matrix* m, float* x, float* y, float* w, float* h)
void RenderRegion::intersect(const RenderRegion& rhs)
{
auto x1 = x + w;
auto y1 = y + h;
auto x2 = rhs.x + rhs.w;
auto y2 = rhs.y + rhs.h;
if (min.x < rhs.min.x) min.x = rhs.min.x;
if (min.y < rhs.min.y) min.y = rhs.min.y;
if (max.x > rhs.max.x) max.x = rhs.max.x;
if (max.y > rhs.max.y) max.y = rhs.max.y;
x = (x > rhs.x) ? x : rhs.x;
y = (y > rhs.y) ? y : rhs.y;
w = ((x1 < x2) ? x1 : x2) - x;
h = ((y1 < y2) ? y1 : y2) - y;
if (w < 0) w = 0;
if (h < 0) h = 0;
}
void RenderRegion::add(const RenderRegion& rhs)
{
if (rhs.x < x) {
w += (x - rhs.x);
x = rhs.x;
}
if (rhs.y < y) {
h += (y - rhs.y);
y = rhs.y;
}
if (rhs.x + rhs.w > x + w) w = (rhs.x + rhs.w) - x;
if (rhs.y + rhs.h > y + h) h = (rhs.y + rhs.h) - y;
// Not intersected: collapse to zero-area region
if (max.x < min.x) max.x = min.x;
if (max.y < min.y) max.y = min.y;
}
/************************************************************************/

View file

@ -94,16 +94,51 @@ struct RenderCompositor
struct RenderRegion
{
int32_t x, y, w, h;
struct {
int32_t x, y;
} min;
struct {
int32_t x, y;
} max;
static constexpr RenderRegion intersect(const RenderRegion& lhs, const RenderRegion& rhs)
{
RenderRegion ret = {{std::max(lhs.min.x, rhs.min.x), std::max(lhs.min.y, rhs.min.y)}, {std::min(lhs.max.x, rhs.max.x), std::min(lhs.max.y, rhs.max.y)}};
// Not intersected: collapse to zero-area region
if (ret.min.x > ret.max.x) ret.max.x = ret.min.x;
if (ret.min.y > ret.max.y) ret.max.y = ret.min.y;
return ret;
}
void intersect(const RenderRegion& rhs);
void add(const RenderRegion& rhs);
void add(const RenderRegion& rhs)
{
if (rhs.min.x < min.x) min.x = rhs.min.x;
if (rhs.min.y < min.y) min.y = rhs.min.y;
if (rhs.max.x > max.x) max.x = rhs.max.x;
if (rhs.max.y > max.y) max.y = rhs.max.y;
}
bool operator==(const RenderRegion& rhs) const
{
if (x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h) return true;
return false;
return (min.x == rhs.min.x && min.y == rhs.min.y && max.x == rhs.max.x && max.y == rhs.max.y);
}
void reset() { min.x = min.y = max.x = max.y = 0; }
bool valid() const { return (max.x > min.x && max.y > min.y); }
bool invalid() const { return !valid(); }
int32_t sx() const { return min.x; }
int32_t sy() const { return min.y; }
int32_t sw() const { return max.x - min.x; }
int32_t sh() const { return max.y - min.y; }
uint32_t x() const { return (uint32_t) sx(); }
uint32_t y() const { return (uint32_t) sy(); }
uint32_t w() const { return (uint32_t) sw(); }
uint32_t h() const { return (uint32_t) sh(); }
};
struct RenderPath
@ -272,7 +307,7 @@ struct RenderShape
struct RenderEffect
{
RenderData rd = nullptr;
RenderRegion extend = {0, 0, 0, 0};
RenderRegion extend{};
SceneEffect type;
bool valid = false;
@ -353,7 +388,7 @@ struct RenderEffectTint : RenderEffect
inst->white[0] = va_arg(args, int);
inst->white[1] = va_arg(args, int);
inst->white[2] = va_arg(args, int);
inst->intensity = (uint8_t)(va_arg(args, double) * 2.55);
inst->intensity = (uint8_t)(static_cast<float>(va_arg(args, double)) * 2.55f);
inst->type = SceneEffect::Tint;
return inst;
}

View file

@ -64,10 +64,11 @@ struct SceneImpl : Scene
{
Paint::Impl impl;
list<Paint*> paints; //children list
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
RenderRegion vport = {};
Array<RenderEffect*>* effects = nullptr;
uint8_t compFlag = CompositionFlag::Invalid;
uint8_t opacity; //for composition
bool vdirty = false;
SceneImpl() : impl(Paint::Impl(this))
{
@ -93,10 +94,11 @@ struct SceneImpl : Scene
//Half translucent requires intermediate composition.
if (opacity == 255) return compFlag;
//If scene has several children or only scene, it may require composition.
//OPTIMIZE: the bitmap type of the picture would not need the composition.
//OPTIMIZE: a single paint of a scene would not need the composition.
if (paints.size() == 1 && paints.front()->type() == Type::Shape) return compFlag;
//Only shape or picture may not require composition.
if (paints.size() == 1) {
auto type = paints.front()->type();
if (type == Type::Shape || type == Type::Picture) return compFlag;
}
compFlag |= CompositionFlag::Opacity;
@ -105,7 +107,7 @@ struct SceneImpl : Scene
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper)
{
this->vport = renderer->viewport();
if (paints.empty()) return nullptr;
if (needComposition(opacity)) {
/* Overriding opacity value. If this scene is half-translucent,
@ -123,11 +125,17 @@ struct SceneImpl : Scene
}
}
//this viewport update is more performant than in bounds()?
vport = renderer->viewport();
vdirty = true;
return nullptr;
}
bool render(RenderMethod* renderer)
{
if (paints.empty()) return true;
RenderCompositor* cmp = nullptr;
auto ret = true;
@ -157,42 +165,38 @@ struct SceneImpl : Scene
return ret;
}
RenderRegion bounds(RenderMethod* renderer) const
RenderRegion bounds(RenderMethod* renderer)
{
if (paints.empty()) return {0, 0, 0, 0};
int32_t x1 = INT32_MAX;
int32_t y1 = INT32_MAX;
int32_t x2 = 0;
int32_t y2 = 0;
for (auto paint : paints) {
auto region = paint->pImpl->bounds(renderer);
if (paints.empty()) return {};
if (!vdirty) return vport;
vdirty = false;
//Merge regions
if (region.x < x1) x1 = region.x;
if (x2 < region.x + region.w) x2 = (region.x + region.w);
if (region.y < y1) y1 = region.y;
if (y2 < region.y + region.h) y2 = (region.y + region.h);
RenderRegion pRegion = {{INT32_MAX, INT32_MAX}, {0, 0}};
for (auto paint : paints) {
auto region = paint->pImpl->bounds(renderer);
if (region.min.x < pRegion.min.x) pRegion.min.x = region.min.x;
if (pRegion.max.x < region.max.x) pRegion.max.x = region.max.x;
if (region.min.y < pRegion.min.y) pRegion.min.y = region.min.y;
if (pRegion.max.y < region.max.y) pRegion.max.y = region.max.y;
}
//Extends the render region if post effects require
int32_t ex = 0, ey = 0, ew = 0, eh = 0;
RenderRegion eRegion{};
if (effects) {
ARRAY_FOREACH(p, *effects) {
auto effect = *p;
if (effect->valid && renderer->region(effect)) {
ex = std::min(ex, effect->extend.x);
ey = std::min(ey, effect->extend.y);
ew = std::max(ew, effect->extend.w);
eh = std::max(eh, effect->extend.h);
}
if (effect->valid && renderer->region(effect)) eRegion.add(effect->extend);
}
}
auto ret = RenderRegion{x1 + ex, y1 + ey, (x2 - x1) + ew, (y2 - y1) + eh};
ret.intersect(this->vport);
return ret;
pRegion.min.x += eRegion.min.x;
pRegion.min.y += eRegion.min.y;
pRegion.max.x += eRegion.max.x;
pRegion.max.y += eRegion.max.y;
vport = RenderRegion::intersect(vport, pRegion);
return vport;
}
Result bounds(Point* pt4, Matrix& m, bool obb, bool stroking)

View file

@ -114,7 +114,7 @@ struct ShapeImpl : Shape
RenderRegion bounds(RenderMethod* renderer)
{
if (!impl.rd) return {0, 0, 0, 0};
if (!impl.rd) return {};
return renderer->region(impl.rd);
}

View file

@ -54,7 +54,7 @@ Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t
if (!renderer) return Result::MemoryCorruption;
if (!renderer->target(buffer, stride, w, h, cs)) return Result::InvalidArguments;
pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
pImpl->vport = {{0, 0}, {(int32_t)w, (int32_t)h}};
renderer->viewport(pImpl->vport);
//FIXME: The value must be associated with an individual canvas instance.

View file

@ -55,7 +55,7 @@ Result WgCanvas::target(void* device, void* instance, void* target, uint32_t w,
if (!renderer) return Result::MemoryCorruption;
if (!renderer->target((WGPUDevice)device, (WGPUInstance)instance, target, w, h, type)) return Result::Unknown;
pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
pImpl->vport = {{0, 0}, {(int32_t)w, (int32_t)h}};
renderer->viewport(pImpl->vport);
//Paints must be updated again with this new target.

View file

@ -7,6 +7,7 @@ source_file = [
'tvgWgRenderData.h',
'tvgWgRenderer.h',
'tvgWgRenderTarget.h',
'tvgWgRenderTask.h',
'tvgWgShaderSrc.h',
'tvgWgShaderTypes.h',
'tvgWgBindGroups.cpp',
@ -17,6 +18,7 @@ source_file = [
'tvgWgRenderData.cpp',
'tvgWgRenderer.cpp',
'tvgWgRenderTarget.cpp',
'tvgWgRenderTask.cpp',
'tvgWgShaderSrc.cpp',
'tvgWgShaderTypes.cpp'
]

View file

@ -103,9 +103,15 @@ WGPUBindGroup WgBindGroupLayouts::createBindGroupStrorage3RO(WGPUTextureView tex
WGPUBindGroup WgBindGroupLayouts::createBindGroupBuffer1Un(WGPUBuffer buff)
{
return createBindGroupBuffer1Un(buff, 0, wgpuBufferGetSize(buff));
}
WGPUBindGroup WgBindGroupLayouts::createBindGroupBuffer1Un(WGPUBuffer buff, uint64_t offset, uint64_t size)
{
const WGPUBindGroupEntry bindGroupEntrys[] = {
{ .binding = 0, .buffer = buff, .size = wgpuBufferGetSize(buff) }
{ .binding = 0, .buffer = buff, .offset = offset, .size = size }
};
const WGPUBindGroupDescriptor bindGroupDesc { .layout = layoutBuffer1Un, .entryCount = 1, .entries = bindGroupEntrys };
return wgpuDeviceCreateBindGroup(device, &bindGroupDesc);

View file

@ -49,6 +49,7 @@ public:
WGPUBindGroup createBindGroupStrorage2RO(WGPUTextureView texView0, WGPUTextureView texView1);
WGPUBindGroup createBindGroupStrorage3RO(WGPUTextureView texView0, WGPUTextureView texView1, WGPUTextureView texView2);
WGPUBindGroup createBindGroupBuffer1Un(WGPUBuffer buff);
WGPUBindGroup createBindGroupBuffer1Un(WGPUBuffer buff, uint64_t offset, uint64_t size);
WGPUBindGroup createBindGroupBuffer2Un(WGPUBuffer buff0, WGPUBuffer buff1);
WGPUBindGroup createBindGroupBuffer3Un(WGPUBuffer buff0, WGPUBuffer buff1, WGPUBuffer buff2);
void releaseBindGroup(WGPUBindGroup& bindGroup);

View file

@ -200,10 +200,7 @@ bool WgContext::allocateBufferVertex(WGPUBuffer& buffer, const float* data, uint
wgpuQueueWriteBuffer(queue, buffer, 0, data, size);
else {
releaseBuffer(buffer);
const WGPUBufferDescriptor bufferDesc {
.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex,
.size = size > WG_VERTEX_BUFFER_MIN_SIZE ? size : WG_VERTEX_BUFFER_MIN_SIZE
};
const WGPUBufferDescriptor bufferDesc { .usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex, .size = size };
buffer = wgpuDeviceCreateBuffer(device, &bufferDesc);
wgpuQueueWriteBuffer(queue, buffer, 0, data, size);
return true;
@ -218,10 +215,7 @@ bool WgContext::allocateBufferIndex(WGPUBuffer& buffer, const uint32_t* data, ui
wgpuQueueWriteBuffer(queue, buffer, 0, data, size);
else {
releaseBuffer(buffer);
const WGPUBufferDescriptor bufferDesc {
.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index,
.size = size > WG_INDEX_BUFFER_MIN_SIZE ? size : WG_INDEX_BUFFER_MIN_SIZE
};
const WGPUBufferDescriptor bufferDesc { .usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index, .size = size };
buffer = wgpuDeviceCreateBuffer(device, &bufferDesc);
wgpuQueueWriteBuffer(queue, buffer, 0, data, size);
return true;
@ -241,10 +235,7 @@ bool WgContext::allocateBufferIndexFan(uint64_t vertexCount)
indexes.push(i + 2);
}
releaseBuffer(bufferIndexFan);
WGPUBufferDescriptor bufferDesc{
.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index,
.size = indexCount * sizeof(uint32_t)
};
WGPUBufferDescriptor bufferDesc{ .usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index, .size = indexCount * sizeof(uint32_t) };
bufferIndexFan = wgpuDeviceCreateBuffer(device, &bufferDesc);
wgpuQueueWriteBuffer(queue, bufferIndexFan, 0, &indexes[0], indexCount * sizeof(uint32_t));
return true;
@ -269,3 +260,34 @@ void WgContext::releaseQueue(WGPUQueue& queue)
queue = nullptr;
}
}
WGPUCommandEncoder WgContext::createCommandEncoder()
{
WGPUCommandEncoderDescriptor commandEncoderDesc{};
return wgpuDeviceCreateCommandEncoder(device, &commandEncoderDesc);
}
void WgContext::submitCommandEncoder(WGPUCommandEncoder commandEncoder)
{
const WGPUCommandBufferDescriptor commandBufferDesc{};
WGPUCommandBuffer commandsBuffer = wgpuCommandEncoderFinish(commandEncoder, &commandBufferDesc);
wgpuQueueSubmit(queue, 1, &commandsBuffer);
wgpuCommandBufferRelease(commandsBuffer);
}
void WgContext::releaseCommandEncoder(WGPUCommandEncoder& commandEncoder)
{
if (commandEncoder) {
wgpuCommandEncoderRelease(commandEncoder);
commandEncoder = nullptr;
}
}
bool WgContext::invalid()
{
return !instance || !device;
}

View file

@ -25,9 +25,6 @@
#include "tvgWgBindGroups.h"
#define WG_VERTEX_BUFFER_MIN_SIZE 2048
#define WG_INDEX_BUFFER_MIN_SIZE 2048
struct WgContext {
// external webgpu handles
WGPUInstance instance{};
@ -70,6 +67,13 @@ struct WgContext {
// release buffer objects
void releaseBuffer(WGPUBuffer& buffer);
// command encoder
WGPUCommandEncoder createCommandEncoder();
void submitCommandEncoder(WGPUCommandEncoder encoder);
void releaseCommandEncoder(WGPUCommandEncoder& encoder);
bool invalid();
};
#endif // _TVG_WG_COMMON_H_

File diff suppressed because it is too large Load diff

View file

@ -38,6 +38,9 @@ class WgCompositor
private:
// pipelines
WgPipelines pipelines{};
// stage buffers
WgStageBufferGeometry stageBufferGeometry{};
WgStageBufferUniform<WgShaderTypePaintSettings> stageBufferPaint;
// global stencil/depth buffer handles
WGPUTexture texDepthStencil{};
WGPUTextureView texViewDepthStencil{};
@ -52,21 +55,26 @@ private:
// current render pass handles
WGPURenderPassEncoder renderPassEncoder{};
WGPUCommandEncoder commandEncoder{};
WgRenderStorage* currentTarget{};
// intermediate render storages
WgRenderStorage storageTemp0;
WgRenderStorage storageTemp1;
WgRenderTarget* currentTarget{};
// intermediate render targets
WgRenderTarget targetTemp0;
WgRenderTarget targetTemp1;
WGPUBindGroup bindGroupStorageTemp{};
// composition and blend geometries
WgMeshData meshData;
WgMeshData meshDataBlit;
// render target dimensions
uint32_t width{};
uint32_t height{};
// viewport utilities
RenderRegion shrinkRenderRegion(RenderRegion& rect);
void copyTexture(const WgRenderStorage* dst, const WgRenderStorage* src);
void copyTexture(const WgRenderStorage* dst, const WgRenderStorage* src, const RenderRegion& region);
void copyTexture(const WgRenderTarget* dst, const WgRenderTarget* src);
void copyTexture(const WgRenderTarget* dst, const WgRenderTarget* src, const RenderRegion& region);
// base meshes draw
void drawMesh(WgContext& context, WgMeshData* meshData);
void drawMeshFan(WgContext& context, WgMeshData* meshData);
void drawMeshImage(WgContext& context, WgMeshData* meshData);
// shapes
void drawShape(WgContext& context, WgRenderDataShape* renderData);
@ -84,8 +92,8 @@ private:
void clipImage(WgContext& context, WgRenderDataPicture* renderData);
// scenes
void drawScene(WgContext& context, WgRenderStorage* scene, WgCompose* compose);
void blendScene(WgContext& context, WgRenderStorage* src, WgCompose* compose);
void drawScene(WgContext& context, WgRenderTarget* scene, WgCompose* compose);
void blendScene(WgContext& context, WgRenderTarget* src, WgCompose* compose);
// the renderer prioritizes clipping with the stroke over the shape's fill
void markupClipPath(WgContext& context, WgRenderDataShape* renderData);
@ -100,24 +108,32 @@ public:
void resize(WgContext& context, uint32_t width, uint32_t height);
// render passes workflow
void beginRenderPass(WGPUCommandEncoder encoder, WgRenderStorage* target, bool clear, WGPUColor clearColor = { 0.0, 0.0, 0.0, 0.0 });
void beginRenderPass(WGPUCommandEncoder encoder, WgRenderTarget* target, bool clear, WGPUColor clearColor = { 0.0, 0.0, 0.0, 0.0 });
void endRenderPass();
// stage buffers operations
void reset(WgContext& context);
void flush(WgContext& context);
// request shapes for drawing (staging)
void requestShape(WgRenderDataShape* renderData);
void requestImage(WgRenderDataPicture* renderData);
// render shapes, images and scenes
void renderShape(WgContext& context, WgRenderDataShape* renderData, BlendMethod blendMethod);
void renderImage(WgContext& context, WgRenderDataPicture* renderData, BlendMethod blendMethod);
void renderScene(WgContext& context, WgRenderStorage* scene, WgCompose* compose);
void composeScene(WgContext& context, WgRenderStorage* src, WgRenderStorage* mask, WgCompose* compose);
void renderScene(WgContext& context, WgRenderTarget* scene, WgCompose* compose);
void composeScene(WgContext& context, WgRenderTarget* src, WgRenderTarget* mask, WgCompose* compose);
// blit render storage to texture view (f.e. screen buffer)
void blit(WgContext& context, WGPUCommandEncoder encoder, WgRenderStorage* src, WGPUTextureView dstView);
// blit render target to texture view (f.e. screen buffer)
void blit(WgContext& context, WGPUCommandEncoder encoder, WgRenderTarget* src, WGPUTextureView dstView);
// effects
bool gaussianBlur(WgContext& context, WgRenderStorage* dst, const RenderEffectGaussianBlur* params, const WgCompose* compose);
bool dropShadow(WgContext& context, WgRenderStorage* dst, const RenderEffectDropShadow* params, const WgCompose* compose);
bool fillEffect(WgContext& context, WgRenderStorage* dst, const RenderEffectFill* params, const WgCompose* compose);
bool tintEffect(WgContext& context, WgRenderStorage* dst, const RenderEffectTint* params, const WgCompose* compose);
bool tritoneEffect(WgContext& context, WgRenderStorage* dst, const RenderEffectTritone* params, const WgCompose* compose);
bool gaussianBlur(WgContext& context, WgRenderTarget* dst, const RenderEffectGaussianBlur* params, const WgCompose* compose);
bool dropShadow(WgContext& context, WgRenderTarget* dst, const RenderEffectDropShadow* params, const WgCompose* compose);
bool fillEffect(WgContext& context, WgRenderTarget* dst, const RenderEffectFill* params, const WgCompose* compose);
bool tintEffect(WgContext& context, WgRenderTarget* dst, const RenderEffectTint* params, const WgCompose* compose);
bool tritoneEffect(WgContext& context, WgRenderTarget* dst, const RenderEffectTritone* params, const WgCompose* compose);
};
#endif // _TVG_WG_COMPOSITOR_H_

View file

@ -167,17 +167,17 @@ void WgPipelines::initialize(WgContext& context)
const WgBindGroupLayouts& layouts = context.layouts;
// bind group layouts helpers
const WGPUBindGroupLayout bindGroupLayoutsStencil[] { layouts.layoutBuffer1Un, layouts.layoutBuffer2Un };
const WGPUBindGroupLayout bindGroupLayoutsDepth[] { layouts.layoutBuffer1Un, layouts.layoutBuffer2Un, layouts.layoutBuffer1Un };
const WGPUBindGroupLayout bindGroupLayoutsStencil[] { layouts.layoutBuffer1Un, layouts.layoutBuffer1Un };
const WGPUBindGroupLayout bindGroupLayoutsDepth[] { layouts.layoutBuffer1Un, layouts.layoutBuffer1Un, layouts.layoutBuffer1Un };
// bind group layouts normal blend
const WGPUBindGroupLayout bindGroupLayoutsSolid[] { layouts.layoutBuffer1Un, layouts.layoutBuffer2Un, layouts.layoutBuffer1Un };
const WGPUBindGroupLayout bindGroupLayoutsGradient[] { layouts.layoutBuffer1Un, layouts.layoutBuffer2Un, layouts.layoutTexSampledBuff2Un };
const WGPUBindGroupLayout bindGroupLayoutsImage[] { layouts.layoutBuffer1Un, layouts.layoutBuffer2Un, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsSolid[] { layouts.layoutBuffer1Un, layouts.layoutBuffer1Un };
const WGPUBindGroupLayout bindGroupLayoutsGradient[] { layouts.layoutBuffer1Un, layouts.layoutBuffer1Un, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsImage[] { layouts.layoutBuffer1Un, layouts.layoutBuffer1Un, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsScene[] { layouts.layoutTexSampled, layouts.layoutBuffer1Un };
// bind group layouts custom blend
const WGPUBindGroupLayout bindGroupLayoutsSolidBlend[] { layouts.layoutBuffer1Un, layouts.layoutBuffer2Un, layouts.layoutBuffer1Un, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsGradientBlend[] { layouts.layoutBuffer1Un, layouts.layoutBuffer2Un, layouts.layoutTexSampledBuff2Un, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsImageBlend[] { layouts.layoutBuffer1Un, layouts.layoutBuffer2Un, layouts.layoutTexSampled, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsSolidBlend[] { layouts.layoutBuffer1Un, layouts.layoutBuffer1Un, layouts.layoutBuffer1Un, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsGradientBlend[] { layouts.layoutBuffer1Un, layouts.layoutBuffer1Un, layouts.layoutTexSampled, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsImageBlend[] { layouts.layoutBuffer1Un, layouts.layoutBuffer1Un, layouts.layoutTexSampled, layouts.layoutTexSampled };
const WGPUBindGroupLayout bindGroupLayoutsSceneBlend[] { layouts.layoutTexSampled, layouts.layoutTexSampled, layouts.layoutBuffer1Un };
// bind group layouts scene compose
const WGPUBindGroupLayout bindGroupLayoutsSceneCompose[] { layouts.layoutTexSampled, layouts.layoutTexSampled };
@ -229,7 +229,7 @@ void WgPipelines::initialize(WgContext& context)
layout_stencil = createPipelineLayout(context.device, bindGroupLayoutsStencil, 2);
layout_depth = createPipelineLayout(context.device, bindGroupLayoutsDepth, 3);
// layouts normal blend
layout_solid = createPipelineLayout(context.device, bindGroupLayoutsSolid, 3);
layout_solid = createPipelineLayout(context.device, bindGroupLayoutsSolid, 2);
layout_gradient = createPipelineLayout(context.device, bindGroupLayoutsGradient, 3);
layout_image = createPipelineLayout(context.device, bindGroupLayoutsImage, 3);
layout_scene = createPipelineLayout(context.device, bindGroupLayoutsScene, 2);

View file

@ -30,163 +30,115 @@
// WgMeshData
//***********************************************************************
void WgMeshData::draw(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
{
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, bufferIndex, WGPUIndexFormat_Uint32, 0, indexCount * sizeof(uint32_t));
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0);
}
void WgMeshData::drawFan(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
{
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, context.bufferIndexFan, WGPUIndexFormat_Uint32, 0, indexCount * sizeof(uint32_t));
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0);
}
void WgMeshData::drawImage(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
{
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 1, bufferTexCoord, 0, vertexCount * sizeof(float) * 2);
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, bufferIndex, WGPUIndexFormat_Uint32, 0, indexCount * sizeof(uint32_t));
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0);
};
void WgMeshData::update(WgContext& context, const WgVertexBuffer& vertexBuffer)
void WgMeshData::update(const WgVertexBuffer& vertexBuffer)
{
assert(vertexBuffer.count > 2);
vertexCount = vertexBuffer.count;
indexCount = (vertexBuffer.count - 2) * 3;
context.allocateBufferVertex(bufferPosition, (float*)vertexBuffer.data, vertexCount * sizeof(float) * 2);
context.allocateBufferIndexFan(vertexCount);
// setup vertex data
vbuffer.reserve(vertexBuffer.count);
vbuffer.count = vertexBuffer.count;
memcpy(vbuffer.data, vertexBuffer.data, sizeof(vertexBuffer.data[0])*vertexBuffer.count);
// setup tex coords data
tbuffer.clear();
}
void WgMeshData::update(WgContext& context, const WgIndexedVertexBuffer& vertexBufferInd)
void WgMeshData::update(const WgIndexedVertexBuffer& vertexBufferInd)
{
assert(vertexBufferInd.vcount > 2);
vertexCount = vertexBufferInd.vcount;
indexCount = vertexBufferInd.icount;
if (vertexCount > 0) context.allocateBufferVertex(bufferPosition, (float*)vertexBufferInd.vbuff, vertexCount * sizeof(float) * 2);
if (indexCount > 0) context.allocateBufferIndex(bufferIndex, vertexBufferInd.ibuff, indexCount * sizeof(uint32_t));
// setup vertex data
vbuffer.reserve(vertexBufferInd.vcount);
vbuffer.count = vertexBufferInd.vcount;
memcpy(vbuffer.data, vertexBufferInd.vbuff, sizeof(vertexBufferInd.vbuff[0])*vertexBufferInd.vcount);
// setup tex coords data
tbuffer.clear();
// copy index data
ibuffer.reserve(vertexBufferInd.icount);
ibuffer.count = vertexBufferInd.icount;
memcpy(ibuffer.data, vertexBufferInd.ibuff, sizeof(vertexBufferInd.ibuff[0])*vertexBufferInd.icount);
};
void WgMeshData::bbox(WgContext& context, const Point pmin, const Point pmax)
void WgMeshData::bbox(const Point pmin, const Point pmax)
{
vertexCount = 4;
indexCount = 6;
const float data[] = {pmin.x, pmin.y, pmax.x, pmin.y, pmax.x, pmax.y, pmin.x, pmax.y};
context.allocateBufferVertex(bufferPosition, data, sizeof(data));
context.allocateBufferIndexFan(vertexCount);
// setup vertex data
vbuffer.reserve(4);
vbuffer.count = 4;
memcpy(vbuffer.data, data, sizeof(data));
// setup tex coords data
tbuffer.clear();
}
void WgMeshData::imageBox(WgContext& context, float w, float h)
void WgMeshData::imageBox(float w, float h)
{
vertexCount = 4;
indexCount = 6;
const float vdata[] = {0.0f, 0.0f, w, 0.0f, w, h, 0.0f, h};
const float tdata[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
const uint32_t idata[] = {0, 1, 2, 0, 2, 3};
context.allocateBufferVertex(bufferPosition, vdata, sizeof(vdata));
context.allocateBufferVertex(bufferTexCoord, tdata, sizeof(tdata));
context.allocateBufferIndex(bufferIndex, idata, sizeof(idata));
// setup vertex data
vbuffer.reserve(4);
vbuffer.count = 4;
memcpy(vbuffer.data, vdata, sizeof(vdata));
// setup tex coords data
tbuffer.reserve(4);
tbuffer.count = 4;
memcpy(tbuffer.data, tdata, sizeof(tdata));
// setup indexes data
ibuffer.reserve(6);
ibuffer.count = 6;
memcpy(ibuffer.data, idata, sizeof(idata));
}
void WgMeshData::blitBox(WgContext& context)
void WgMeshData::blitBox()
{
vertexCount = 4;
indexCount = 6;
const float vdata[] = {-1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, -1.0f, -1.0f};
const float tdata[] = {+0.0f, +0.0f, +1.0f, +0.0f, +1.0f, +1.0f, +0.0f, +1.0f};
const uint32_t idata[] = { 0, 1, 2, 0, 2, 3 };
context.allocateBufferVertex(bufferPosition, vdata, sizeof(vdata));
context.allocateBufferVertex(bufferTexCoord, tdata, sizeof(tdata));
context.allocateBufferIndex(bufferIndex, idata, sizeof(idata));
// setup vertex data
vbuffer.reserve(4);
vbuffer.count = 4;
memcpy(vbuffer.data, vdata, sizeof(vdata));
// setup tex coords data
tbuffer.reserve(4);
tbuffer.count = 4;
memcpy(tbuffer.data, tdata, sizeof(tdata));
// setup indexes data
ibuffer.reserve(6);
ibuffer.count = 6;
memcpy(ibuffer.data, idata, sizeof(idata));
}
void WgMeshData::release(WgContext& context)
{
context.releaseBuffer(bufferIndex);
context.releaseBuffer(bufferTexCoord);
context.releaseBuffer(bufferPosition);
};
//***********************************************************************
// WgMeshDataPool
//***********************************************************************
WgMeshData* WgMeshDataPool::allocate(WgContext& context)
{
WgMeshData* meshData{};
if (mPool.count > 0) {
meshData = mPool.last();
mPool.pop();
} else {
meshData = new WgMeshData();
mList.push(meshData);
}
return meshData;
}
void WgMeshDataPool::free(WgContext& context, WgMeshData* meshData)
{
mPool.push(meshData);
}
void WgMeshDataPool::release(WgContext& context)
{
ARRAY_FOREACH(p, mList) {
(*p)->release(context);
delete(*p);
}
mPool.clear();
mList.clear();
}
WgMeshDataPool gMeshDataPoolInstance;
WgMeshDataPool* WgMeshDataPool::gMeshDataPool = &gMeshDataPoolInstance;
//***********************************************************************
// WgMeshDataGroup
//***********************************************************************
void WgMeshDataGroup::append(WgContext& context, const WgVertexBuffer& vertexBuffer)
void WgMeshDataGroup::append(const WgVertexBuffer& vertexBuffer)
{
assert(vertexBuffer.count >= 3);
meshes.push(WgMeshDataPool::gMeshDataPool->allocate(context));
meshes.last()->update(context, vertexBuffer);
meshes.push(new WgMeshData());
meshes.last()->update(vertexBuffer);
}
void WgMeshDataGroup::append(WgContext& context, const WgIndexedVertexBuffer& vertexBufferInd)
void WgMeshDataGroup::append(const WgIndexedVertexBuffer& vertexBufferInd)
{
assert(vertexBufferInd.vcount >= 3);
meshes.push(WgMeshDataPool::gMeshDataPool->allocate(context));
meshes.last()->update(context, vertexBufferInd);
meshes.push(new WgMeshData());
meshes.last()->update(vertexBufferInd);
}
void WgMeshDataGroup::append(WgContext& context, const Point pmin, const Point pmax)
void WgMeshDataGroup::append(const Point pmin, const Point pmax)
{
meshes.push(WgMeshDataPool::gMeshDataPool->allocate(context));
meshes.last()->bbox(context, pmin, pmax);
meshes.push(new WgMeshData());
meshes.last()->bbox(pmin, pmax);
}
void WgMeshDataGroup::release(WgContext& context)
void WgMeshDataGroup::release()
{
ARRAY_FOREACH(p, meshes)
WgMeshDataPool::gMeshDataPool->free(context, *p);
ARRAY_FOREACH(p, meshes) delete *p;
meshes.clear();
};
@ -209,12 +161,38 @@ void WgImageData::update(WgContext& context, const RenderSurface* surface)
if (texHandleChanged) {
context.releaseTextureView(textureView);
textureView = context.createTextureView(texture);
// update bind group
context.layouts.releaseBindGroup(bindGroup);
bindGroup = context.layouts.createBindGroupTexSampled(context.samplerLinearRepeat, textureView);
}
};
void WgImageData::update(WgContext& context, const Fill* fill)
{
// compute gradient data
WgShaderTypeGradientData gradientData;
gradientData.update(fill);
// allocate new texture handle
bool texHandleChanged = context.allocateTexture(texture, WG_TEXTURE_GRADIENT_SIZE, 1, WGPUTextureFormat_RGBA8Unorm, gradientData.data);
// update texture view of texture handle was changed
if (texHandleChanged) {
context.releaseTextureView(textureView);
textureView = context.createTextureView(texture);
// get sampler by spread type
WGPUSampler sampler = context.samplerLinearClamp;
if (fill->spread() == FillSpread::Reflect) sampler = context.samplerLinearMirror;
if (fill->spread() == FillSpread::Repeat) sampler = context.samplerLinearRepeat;
// update bind group
context.layouts.releaseBindGroup(bindGroup);
bindGroup = context.layouts.createBindGroupTexSampled(sampler, textureView);
}
};
void WgImageData::release(WgContext& context)
{
context.layouts.releaseBindGroup(bindGroup);
context.releaseTextureView(textureView);
context.releaseTexture(texture);
};
@ -223,52 +201,31 @@ void WgImageData::release(WgContext& context)
// WgRenderSettings
//***********************************************************************
void WgRenderSettings::updateFill(WgContext& context, const Fill* fill)
void WgRenderSettings::update(WgContext& context, const tvg::Matrix& transform, tvg::ColorSpace cs, uint8_t opacity)
{
rasterType = WgRenderRasterType::Gradient;
// get gradient transfrom matrix
Matrix invFillTransform;
WgShaderTypeMat4x4f gradientTrans; // identity by default
if (inverse(&fill->transform(), &invFillTransform))
gradientTrans.update(invFillTransform);
settings.transform.update(transform);
settings.options.update(cs, opacity);
}
void WgRenderSettings::update(WgContext& context, const Fill* fill)
{
assert(fill);
settings.gradient.update(fill);
gradientData.update(context, fill);
// get gradient rasterisation settings
WgShaderTypeGradient gradient;
if (fill->type() == Type::LinearGradient) {
gradient.update((LinearGradient*)fill);
rasterType = WgRenderRasterType::Gradient;
if (fill->type() == Type::LinearGradient)
fillType = WgRenderSettingsType::Linear;
} else if (fill->type() == Type::RadialGradient) {
gradient.update((RadialGradient*)fill);
else if (fill->type() == Type::RadialGradient)
fillType = WgRenderSettingsType::Radial;
}
// update gpu assets
bool bufferGradientSettingsChanged = context.allocateBufferUniform(bufferGroupGradient, &gradient.settings, sizeof(gradient.settings));
bool bufferGradientTransformChanged = context.allocateBufferUniform(bufferGroupTransfromGrad, &gradientTrans.mat, sizeof(gradientTrans.mat));
bool textureGradientChanged = context.allocateTexture(texGradient, WG_TEXTURE_GRADIENT_SIZE, 1, WGPUTextureFormat_RGBA8Unorm, gradient.texData);
if (bufferGradientSettingsChanged || textureGradientChanged || bufferGradientTransformChanged) {
// update texture view
context.releaseTextureView(texViewGradient);
texViewGradient = context.createTextureView(texGradient);
// get sampler by spread type
WGPUSampler sampler = context.samplerLinearClamp;
if (fill->spread() == FillSpread::Reflect) sampler = context.samplerLinearMirror;
if (fill->spread() == FillSpread::Repeat) sampler = context.samplerLinearRepeat;
// update bind group
context.layouts.releaseBindGroup(bindGroupGradient);
bindGroupGradient = context.layouts.createBindGroupTexSampledBuff2Un(
sampler, texViewGradient, bufferGroupGradient, bufferGroupTransfromGrad);
}
skip = false;
};
void WgRenderSettings::updateColor(WgContext& context, const RenderColor& c)
void WgRenderSettings::update(WgContext& context, const RenderColor& c)
{
settings.color.update(c);
rasterType = WgRenderRasterType::Solid;
WgShaderTypeVec4f solidColor(c);
if (context.allocateBufferUniform(bufferGroupSolid, &solidColor, sizeof(solidColor))) {
context.layouts.releaseBindGroup(bindGroupSolid);
bindGroupSolid = context.layouts.createBindGroupBuffer1Un(bufferGroupSolid);
}
fillType = WgRenderSettingsType::Solid;
skip = (c.a == 0);
};
@ -276,13 +233,7 @@ void WgRenderSettings::updateColor(WgContext& context, const RenderColor& c)
void WgRenderSettings::release(WgContext& context)
{
context.layouts.releaseBindGroup(bindGroupSolid);
context.layouts.releaseBindGroup(bindGroupGradient);
context.releaseBuffer(bufferGroupSolid);
context.releaseBuffer(bufferGroupGradient);
context.releaseBuffer(bufferGroupTransfromGrad);
context.releaseTexture(texGradient);
context.releaseTextureView(texViewGradient);
gradientData.release(context);
};
//***********************************************************************
@ -291,26 +242,10 @@ void WgRenderSettings::release(WgContext& context)
void WgRenderDataPaint::release(WgContext& context)
{
context.layouts.releaseBindGroup(bindGroupPaint);
context.releaseBuffer(bufferModelMat);
context.releaseBuffer(bufferBlendSettings);
clips.clear();
};
void WgRenderDataPaint::update(WgContext& context, const tvg::Matrix& transform, tvg::ColorSpace cs, uint8_t opacity)
{
WgShaderTypeMat4x4f modelMat(transform);
WgShaderTypeVec4f blendSettings(cs, opacity);
bool bufferModelMatChanged = context.allocateBufferUniform(bufferModelMat, &modelMat, sizeof(modelMat));
bool bufferBlendSettingsChanged = context.allocateBufferUniform(bufferBlendSettings, &blendSettings, sizeof(blendSettings));
if (bufferModelMatChanged || bufferBlendSettingsChanged) {
context.layouts.releaseBindGroup(bindGroupPaint);
bindGroupPaint = context.layouts.createBindGroupBuffer2Un(bufferModelMat, bufferBlendSettings);
}
}
void WgRenderDataPaint::updateClips(tvg::Array<tvg::RenderData> &clips) {
this->clips.clear();
ARRAY_FOREACH(p, clips) {
@ -322,24 +257,24 @@ void WgRenderDataPaint::updateClips(tvg::Array<tvg::RenderData> &clips) {
// WgRenderDataShape
//***********************************************************************
void WgRenderDataShape::appendShape(WgContext& context, const WgVertexBuffer& vertexBuffer)
void WgRenderDataShape::appendShape(const WgVertexBuffer& vertexBuffer)
{
if (vertexBuffer.count < 3) return;
Point pmin{}, pmax{};
vertexBuffer.getMinMax(pmin, pmax);
meshGroupShapes.append(context, vertexBuffer);
meshGroupShapesBBox.append(context, pmin, pmax);
meshGroupShapes.append(vertexBuffer);
meshGroupShapesBBox.append(pmin, pmax);
updateBBox(pmin, pmax);
}
void WgRenderDataShape::appendStroke(WgContext& context, const WgIndexedVertexBuffer& vertexBufferInd)
void WgRenderDataShape::appendStroke(const WgIndexedVertexBuffer& vertexBufferInd)
{
if (vertexBufferInd.vcount < 3) return;
Point pmin{}, pmax{};
vertexBufferInd.getMinMax(pmin, pmax);
meshGroupStrokes.append(context, vertexBufferInd);
meshGroupStrokesBBox.append(context, pmin, pmax);
meshGroupStrokes.append(vertexBufferInd);
meshGroupStrokesBBox.append(pmin, pmax);
updateBBox(pmin, pmax);
}
@ -358,14 +293,14 @@ void WgRenderDataShape::updateAABB(const Matrix& tr) {
auto p1 = Point{pMax.x, pMin.y} * tr;
auto p2 = Point{pMin.x, pMax.y} * tr;
auto p3 = Point{pMax.x, pMax.y} * tr;
aabb.min = {std::min({p0.x, p1.x, p2.x, p3.x}), std::min({p0.y, p1.y, p2.y, p3.y})};
aabb.max = {std::max({p0.x, p1.x, p2.x, p3.x}), std::max({p0.y, p1.y, p2.y, p3.y})};
aabb.min = {std::min(std::min(p0.x, p1.x), std::min(p2.x, p3.x)), std::min(std::min(p0.y, p1.y), std::min(p2.y, p3.y))};
aabb.max = {std::max(std::max(p0.x, p1.x), std::max(p2.x, p3.x)), std::max(std::max(p0.y, p1.y), std::max(p2.y, p3.y))};
}
void WgRenderDataShape::updateMeshes(WgContext& context, const RenderShape &rshape, const Matrix& tr, WgGeometryBufferPool* pool)
void WgRenderDataShape::updateMeshes(const RenderShape &rshape, const Matrix& tr, WgGeometryBufferPool* pool)
{
releaseMeshes(context);
releaseMeshes();
strokeFirst = rshape.strokeFirst();
// get object scale
@ -375,8 +310,8 @@ void WgRenderDataShape::updateMeshes(WgContext& context, const RenderShape &rsha
auto pbuff = pool->reqVertexBuffer(scale);
pbuff->decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) {
appendShape(context, path_buff);
if ((rshape.stroke) && (rshape.stroke->width > 0)) proceedStrokes(context, rshape.stroke, path_buff, pool);
appendShape(path_buff);
if ((rshape.stroke) && (rshape.stroke->width > 0)) proceedStrokes(rshape.stroke, path_buff, pool);
}, rshape.trimpath());
// update shapes bbox (with empty path handling)
@ -384,31 +319,31 @@ void WgRenderDataShape::updateMeshes(WgContext& context, const RenderShape &rsha
(this->meshGroupStrokesBBox.meshes.count > 0)) {
updateAABB(tr);
} else aabb = {{0, 0}, {0, 0}};
meshDataBBox.bbox(context, pMin, pMax);
meshDataBBox.bbox(pMin, pMax);
pool->retVertexBuffer(pbuff);
}
void WgRenderDataShape::proceedStrokes(WgContext& context, const RenderStroke* rstroke, const WgVertexBuffer& buff, WgGeometryBufferPool* pool)
void WgRenderDataShape::proceedStrokes(const RenderStroke* rstroke, const WgVertexBuffer& buff, WgGeometryBufferPool* pool)
{
assert(rstroke);
auto strokesGenerator = pool->reqIndexedVertexBuffer(buff.scale);
if (rstroke->dash.count == 0) strokesGenerator->appendStrokes(buff, rstroke);
else strokesGenerator->appendStrokesDashed(buff, rstroke);
appendStroke(context, *strokesGenerator);
appendStroke(*strokesGenerator);
pool->retIndexedVertexBuffer(strokesGenerator);
}
void WgRenderDataShape::releaseMeshes(WgContext& context)
void WgRenderDataShape::releaseMeshes()
{
meshGroupStrokesBBox.release(context);
meshGroupStrokes.release(context);
meshGroupShapesBBox.release(context);
meshGroupShapes.release(context);
meshGroupStrokesBBox.release();
meshGroupStrokes.release();
meshGroupShapesBBox.release();
meshGroupShapes.release();
pMin = {FLT_MAX, FLT_MAX};
pMax = {0.0f, 0.0f};
aabb = {{0, 0}, {0, 0}};
@ -418,8 +353,7 @@ void WgRenderDataShape::releaseMeshes(WgContext& context)
void WgRenderDataShape::release(WgContext& context)
{
releaseMeshes(context);
meshDataBBox.release(context);
releaseMeshes();
renderSettingsStroke.release(context);
renderSettingsShape.release(context);
WgRenderDataPaint::release(context);
@ -445,10 +379,10 @@ WgRenderDataShape* WgRenderDataShapePool::allocate(WgContext& context)
void WgRenderDataShapePool::free(WgContext& context, WgRenderDataShape* renderData)
{
renderData->meshGroupShapes.release(context);
renderData->meshGroupShapesBBox.release(context);
renderData->meshGroupStrokes.release(context);
renderData->meshGroupStrokesBBox.release(context);
renderData->meshGroupShapes.release();
renderData->meshGroupShapesBBox.release();
renderData->meshGroupStrokes.release();
renderData->meshGroupStrokesBBox.release();
renderData->clips.clear();
mPool.push(renderData);
}
@ -471,22 +405,16 @@ void WgRenderDataShapePool::release(WgContext& context)
void WgRenderDataPicture::updateSurface(WgContext& context, const RenderSurface* surface)
{
// upoate mesh data
meshData.imageBox(context, surface->w, surface->h);
meshData.imageBox(surface->w, surface->h);
// update texture data
imageData.update(context, surface);
// update texture bind group
context.layouts.releaseBindGroup(bindGroupPicture);
bindGroupPicture = context.layouts.createBindGroupTexSampled(
context.samplerLinearRepeat, imageData.textureView
);
}
void WgRenderDataPicture::release(WgContext& context)
{
context.layouts.releaseBindGroup(bindGroupPicture);
renderSettings.release(context);
imageData.release(context);
meshData.release(context);
WgRenderDataPaint::release(context);
}
@ -681,3 +609,85 @@ void WgRenderDataEffectParamsPool::release(WgContext& context)
mPool.clear();
mList.clear();
}
//***********************************************************************
// WgStageBufferGeometry
//***********************************************************************
void WgStageBufferGeometry::append(WgMeshData* meshData)
{
assert(meshData);
uint32_t vsize = meshData->vbuffer.count * sizeof(meshData->vbuffer[0]);
uint32_t tsize = meshData->tbuffer.count * sizeof(meshData->tbuffer[0]);
uint32_t isize = meshData->ibuffer.count * sizeof(meshData->ibuffer[0]);
vmaxcount = std::max(vmaxcount, meshData->vbuffer.count);
// append vertex data
if (vbuffer.reserved < vbuffer.count + vsize)
vbuffer.grow(std::max(vsize, vbuffer.reserved));
if (meshData->vbuffer.count > 0) {
meshData->voffset = vbuffer.count;
memcpy(vbuffer.data + vbuffer.count, meshData->vbuffer.data, vsize);
vbuffer.count += vsize;
}
// append tex coords data
if (vbuffer.reserved < vbuffer.count + tsize)
vbuffer.grow(std::max(tsize, vbuffer.reserved));
if (meshData->tbuffer.count > 0) {
meshData->toffset = vbuffer.count;
memcpy(vbuffer.data + vbuffer.count, meshData->tbuffer.data, tsize);
vbuffer.count += tsize;
}
// append index data
if (ibuffer.reserved < ibuffer.count + isize)
ibuffer.grow(std::max(isize, ibuffer.reserved));
if (meshData->ibuffer.count > 0) {
meshData->ioffset = ibuffer.count;
memcpy(ibuffer.data + ibuffer.count, meshData->ibuffer.data, isize);
ibuffer.count += isize;
}
}
void WgStageBufferGeometry::append(WgMeshDataGroup* meshDataGroup)
{
ARRAY_FOREACH(p, meshDataGroup->meshes) append(*p);
}
void WgStageBufferGeometry::append(WgRenderDataShape* renderDataShape)
{
append(&renderDataShape->meshGroupShapes);
append(&renderDataShape->meshGroupShapesBBox);
append(&renderDataShape->meshGroupStrokes);
append(&renderDataShape->meshGroupStrokesBBox);
append(&renderDataShape->meshDataBBox);
}
void WgStageBufferGeometry::append(WgRenderDataPicture* renderDataPicture)
{
append(&renderDataPicture->meshData);
}
void WgStageBufferGeometry::release(WgContext& context)
{
context.releaseBuffer(vbuffer_gpu);
context.releaseBuffer(ibuffer_gpu);
}
void WgStageBufferGeometry::clear()
{
vbuffer.clear();
ibuffer.clear();
vmaxcount = 0;
}
void WgStageBufferGeometry::flush(WgContext& context)
{
context.allocateBufferVertex(vbuffer_gpu, (float *)vbuffer.data, vbuffer.count);
context.allocateBufferIndex(ibuffer_gpu, (uint32_t *)ibuffer.data, ibuffer.count);
context.allocateBufferIndexFan(vmaxcount);
}

View file

@ -28,49 +28,36 @@
#include "tvgWgShaderTypes.h"
struct WgMeshData {
WGPUBuffer bufferPosition{};
WGPUBuffer bufferTexCoord{};
WGPUBuffer bufferIndex{};
size_t vertexCount{};
size_t indexCount{};
Array<Point> vbuffer;
Array<Point> tbuffer;
Array<uint32_t> ibuffer;
size_t voffset{};
size_t toffset{};
size_t ioffset{};
void draw(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
void drawFan(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
void drawImage(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
void update(WgContext& context, const WgVertexBuffer& vertexBuffer);
void update(WgContext& context, const WgIndexedVertexBuffer& vertexBufferInd);
void bbox(WgContext& context, const Point pmin, const Point pmax);
void imageBox(WgContext& context, float w, float h);
void blitBox(WgContext& context);
void release(WgContext& context);
};
class WgMeshDataPool {
private:
Array<WgMeshData*> mPool;
Array<WgMeshData*> mList;
public:
static WgMeshDataPool* gMeshDataPool;
WgMeshData* allocate(WgContext& context);
void free(WgContext& context, WgMeshData* meshData);
void release(WgContext& context);
void update(const WgVertexBuffer& vertexBuffer);
void update(const WgIndexedVertexBuffer& vertexBufferInd);
void bbox(const Point pmin, const Point pmax);
void imageBox(float w, float h);
void blitBox();
};
struct WgMeshDataGroup {
Array<WgMeshData*> meshes{};
void append(WgContext& context, const WgVertexBuffer& vertexBuffer);
void append(WgContext& context, const WgIndexedVertexBuffer& vertexBufferInd);
void append(WgContext& context, const Point pmin, const Point pmax);
void release(WgContext& context);
void append(const WgVertexBuffer& vertexBuffer);
void append(const WgIndexedVertexBuffer& vertexBufferInd);
void append(const Point pmin, const Point pmax);
void release();
};
struct WgImageData {
WGPUTexture texture{};
WGPUTextureView textureView{};
WGPUBindGroup bindGroup{};
void update(WgContext& context, const RenderSurface* surface);
void update(WgContext& context, const Fill* fill);
void release(WgContext& context);
};
@ -79,37 +66,29 @@ enum class WgRenderRasterType { Solid = 0, Gradient, Image };
struct WgRenderSettings
{
WGPUBuffer bufferGroupSolid{};
WGPUBindGroup bindGroupSolid{};
WGPUTexture texGradient{};
WGPUTextureView texViewGradient{};
WGPUBuffer bufferGroupGradient{};
WGPUBuffer bufferGroupTransfromGrad{};
WGPUBindGroup bindGroupGradient{};
uint32_t bindGroupInd{};
WgShaderTypePaintSettings settings;
WgImageData gradientData;
WgRenderSettingsType fillType{};
WgRenderRasterType rasterType{};
bool skip{};
void updateFill(WgContext& context, const Fill* fill);
void updateColor(WgContext& context, const RenderColor& c);
void update(WgContext& context, const tvg::Matrix& transform, tvg::ColorSpace cs, uint8_t opacity);
void update(WgContext& context, const Fill* fill);
void update(WgContext& context, const RenderColor& c);
void release(WgContext& context);
};
struct WgRenderDataPaint
{
WGPUBuffer bufferModelMat{};
WGPUBuffer bufferBlendSettings{};
WGPUBindGroup bindGroupPaint{};
RenderRegion viewport{};
BBox aabb{{},{}};
float opacity{};
RenderRegion viewport{};
Array<WgRenderDataPaint*> clips;
virtual ~WgRenderDataPaint() {};
virtual void release(WgContext& context);
virtual Type type() { return Type::Undefined; };
void update(WgContext& context, const tvg::Matrix& transform, tvg::ColorSpace cs, uint8_t opacity);
void updateClips(tvg::Array<tvg::RenderData> &clips);
};
@ -127,13 +106,13 @@ struct WgRenderDataShape: public WgRenderDataPaint
bool strokeFirst{};
FillRule fillRule{};
void appendShape(WgContext& context, const WgVertexBuffer& vertexBuffer);
void appendStroke(WgContext& context, const WgIndexedVertexBuffer& vertexBufferInd);
void appendShape(const WgVertexBuffer& vertexBuffer);
void appendStroke(const WgIndexedVertexBuffer& vertexBufferInd);
void updateBBox(Point pmin, Point pmax);
void updateAABB(const Matrix& tr);
void updateMeshes(WgContext& context, const RenderShape& rshape, const Matrix& tr, WgGeometryBufferPool* pool);
void proceedStrokes(WgContext& context, const RenderStroke* rstroke, const WgVertexBuffer& buff, WgGeometryBufferPool* pool);
void releaseMeshes(WgContext& context);
void updateMeshes(const RenderShape& rshape, const Matrix& tr, WgGeometryBufferPool* pool);
void proceedStrokes(const RenderStroke* rstroke, const WgVertexBuffer& buff, WgGeometryBufferPool* pool);
void releaseMeshes();
void release(WgContext& context) override;
Type type() override { return Type::Shape; };
};
@ -150,7 +129,7 @@ public:
struct WgRenderDataPicture: public WgRenderDataPaint
{
WGPUBindGroup bindGroupPicture{};
WgRenderSettings renderSettings{};
WgImageData imageData{};
WgMeshData meshData{};
@ -224,4 +203,69 @@ public:
void release(WgContext& context);
};
class WgStageBufferGeometry {
private:
Array<uint8_t> vbuffer;
Array<uint8_t> ibuffer;
uint32_t vmaxcount{};
public:
WGPUBuffer vbuffer_gpu{};
WGPUBuffer ibuffer_gpu{};
void append(WgMeshData* meshData);
void append(WgMeshDataGroup* meshDataGroup);
void append(WgRenderDataShape* renderDataShape);
void append(WgRenderDataPicture* renderDataPicture);
void initialize(WgContext& context){};
void release(WgContext& context);
void clear();
void flush(WgContext& context);
};
// typed uniform stage buffer with related bind groups handling
template<typename T>
class WgStageBufferUniform {
private:
Array<T> ubuffer;
WGPUBuffer ubuffer_gpu{};
Array<WGPUBindGroup> bbuffer;
public:
// append uniform data to cpu staged buffer and return related bind group index
uint32_t append(const T& value) {
ubuffer.push(value);
return ubuffer.count - 1;
}
void flush(WgContext& context) {
// flush data to gpu buffer from cpu memory including reserved data to prevent future gpu buffer reallocations
bool bufferChanged = context.allocateBufferUniform(ubuffer_gpu, (void*)ubuffer.data, ubuffer.reserved*sizeof(T));
// if gpu buffer handle was changed we must to remove all created binding groups
if (bufferChanged) releaseBindGroups(context);
// allocate bind groups for all new data items
for (uint32_t i = bbuffer.count; i < ubuffer.count; i++)
bbuffer.push(context.layouts.createBindGroupBuffer1Un(ubuffer_gpu, i*sizeof(T), sizeof(T)));
assert(bbuffer.count >= ubuffer.count);
}
// please, use index that was returned from append method
WGPUBindGroup operator[](const uint32_t index) const {
return bbuffer[index];
}
void clear() {
ubuffer.clear();
}
void release(WgContext& context) {
context.releaseBuffer(ubuffer_gpu);
releaseBindGroups(context);
}
void releaseBindGroups(WgContext& context) {
ARRAY_FOREACH(p, bbuffer)
context.layouts.releaseBindGroup(*p);
bbuffer.clear();
}
};
#endif // _TVG_WG_RENDER_DATA_H_

View file

@ -22,7 +22,7 @@
#include "tvgWgRenderTarget.h"
void WgRenderStorage::initialize(WgContext& context, uint32_t width, uint32_t height)
void WgRenderTarget::initialize(WgContext& context, uint32_t width, uint32_t height)
{
this->width = width;
this->height = height;
@ -36,7 +36,7 @@ void WgRenderStorage::initialize(WgContext& context, uint32_t width, uint32_t he
}
void WgRenderStorage::release(WgContext& context)
void WgRenderTarget::release(WgContext& context)
{
context.layouts.releaseBindGroup(bindGroupTexure);
context.layouts.releaseBindGroup(bindGroupWrite);
@ -50,38 +50,38 @@ void WgRenderStorage::release(WgContext& context)
}
//*****************************************************************************
// render storage pool
// render target pool
//*****************************************************************************
WgRenderStorage* WgRenderStoragePool::allocate(WgContext& context)
WgRenderTarget* WgRenderTargetPool::allocate(WgContext& context)
{
WgRenderStorage* renderStorage{};
WgRenderTarget* renderTarget{};
if (pool.count > 0) {
renderStorage = pool.last();
renderTarget = pool.last();
pool.pop();
} else {
renderStorage = new WgRenderStorage;
renderStorage->initialize(context, width, height);
list.push(renderStorage);
renderTarget = new WgRenderTarget;
renderTarget->initialize(context, width, height);
list.push(renderTarget);
}
return renderStorage;
return renderTarget;
};
void WgRenderStoragePool::free(WgContext& context, WgRenderStorage* renderStorage)
void WgRenderTargetPool::free(WgContext& context, WgRenderTarget* renderTarget)
{
pool.push(renderStorage);
pool.push(renderTarget);
};
void WgRenderStoragePool::initialize(WgContext& context, uint32_t width, uint32_t height)
void WgRenderTargetPool::initialize(WgContext& context, uint32_t width, uint32_t height)
{
this->width = width;
this->height = height;
}
void WgRenderStoragePool::release(WgContext& context)
void WgRenderTargetPool::release(WgContext& context)
{
ARRAY_FOREACH(p, list) {
(*p)->release(context);

View file

@ -26,7 +26,7 @@
#include "tvgWgPipelines.h"
#include "tvgRender.h"
struct WgRenderStorage {
struct WgRenderTarget {
WGPUTexture texture{};
WGPUTexture textureMS{};
WGPUTextureView texView{};
@ -42,15 +42,15 @@ struct WgRenderStorage {
};
class WgRenderStoragePool {
class WgRenderTargetPool {
private:
Array<WgRenderStorage*> list;
Array<WgRenderStorage*> pool;
Array<WgRenderTarget*> list;
Array<WgRenderTarget*> pool;
uint32_t width{};
uint32_t height{};
public:
WgRenderStorage* allocate(WgContext& context);
void free(WgContext& context, WgRenderStorage* renderTarget);
WgRenderTarget* allocate(WgContext& context);
void free(WgContext& context, WgRenderTarget* renderTarget);
void initialize(WgContext& context, uint32_t width, uint32_t height);
void release(WgContext& context);

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2025 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgWgRenderTask.h"
#include <iostream>
//***********************************************************************
// WgPaintTask
//***********************************************************************
void WgPaintTask::run(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder)
{
if (renderData->type() == tvg::Type::Shape)
compositor.renderShape(context, (WgRenderDataShape*)renderData, blendMethod);
if (renderData->type() == tvg::Type::Picture)
compositor.renderImage(context, (WgRenderDataPicture*)renderData, blendMethod);
else assert(true);
}
//***********************************************************************
// WgSceneTask
//***********************************************************************
void WgSceneTask::run(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder)
{
// begin the render pass for the current scene and clear the target content
WGPUColor color{};
if ((compose->method == MaskMethod::None) && (compose->blend != BlendMethod::Normal)) color = { 1.0, 1.0, 1.0, 0.0 };
compositor.beginRenderPass(encoder, renderTarget, true, color);
// run all childs (scenes and shapes)
runChildren(context, compositor, encoder);
// we must to end current render pass for current scene
compositor.endRenderPass();
// we must to apply effect for current scene
if (effect)
runEffect(context, compositor, encoder);
// there's no point in continuing if the scene has no destination target (e.g., the root scene)
if (!renderTargetDst) return;
// apply scene blending
if (compose->method == MaskMethod::None) {
compositor.beginRenderPass(encoder, renderTargetDst, false);
compositor.renderScene(context, renderTarget, compose);
// apply scene composition (for scenes, that have a handle to mask)
} else if (renderTargetMsk) {
compositor.beginRenderPass(encoder, renderTargetDst, false);
compositor.composeScene(context, renderTarget, renderTargetMsk, compose);
}
}
void WgSceneTask::runChildren(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder)
{
ARRAY_FOREACH(task, children) {
WgRenderTask* renderTask = *task;
// we need to restore current render pass without clear
compositor.beginRenderPass(encoder, renderTarget, false);
// run children (shape or scene)
renderTask->run(context, compositor, encoder);
}
}
void WgSceneTask::runEffect(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder)
{
assert(effect);
switch (effect->type) {
case SceneEffect::GaussianBlur: compositor.gaussianBlur(context, renderTarget, (RenderEffectGaussianBlur*)effect, compose); break;
case SceneEffect::DropShadow: compositor.dropShadow(context, renderTarget, (RenderEffectDropShadow*)effect, compose); break;
case SceneEffect::Fill: compositor.fillEffect(context, renderTarget, (RenderEffectFill*)effect, compose); break;
case SceneEffect::Tint: compositor.tintEffect(context, renderTarget, (RenderEffectTint*)effect, compose); break;
case SceneEffect::Tritone : compositor.tritoneEffect(context, renderTarget, (RenderEffectTritone*)effect, compose); break;
default: break;
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2025 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_WG_RENDER_TASK_H_
#define _TVG_WG_RENDER_TASK_H_
#include "tvgWgCompositor.h"
// base class for any renderable objects
struct WgRenderTask {
virtual ~WgRenderTask() {}
virtual void run(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder) = 0;
};
// task for sinlge shape rendering
struct WgPaintTask: public WgRenderTask {
// shape render properties
WgRenderDataPaint* renderData{};
BlendMethod blendMethod{};
WgPaintTask(WgRenderDataPaint* renderData, BlendMethod blendMethod) :
renderData(renderData), blendMethod(blendMethod) {}
// apply shape execution, including custom blending and clipping
void run(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder) override;
};
// task for scene rendering with blending, composition and effect
struct WgSceneTask: public WgRenderTask {
public:
// parent scene (nullptr for root scene)
WgSceneTask* parent{};
// childs can be shapes or scenes tesks
Array<WgRenderTask*> children;
// scene blend/compose targets
WgRenderTarget* renderTarget{};
WgRenderTarget* renderTargetMsk{};
WgRenderTarget* renderTargetDst{};
// scene blend/compose properties
WgCompose* compose{};
// scene effect properties
const RenderEffect* effect{};
WgSceneTask(WgRenderTarget* renderTarget, WgCompose* compose, WgSceneTask* parent) :
parent(parent), renderTarget(renderTarget), compose(compose) {}
// run all, including all shapes drawing, blending, composition and effect
void run(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder) override;
private:
void runChildren(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder);
void runEffect(WgContext& context, WgCompositor& compositor, WGPUCommandEncoder encoder);
};
#endif // _TVG_WG_RENDER_TASK_H_

View file

@ -34,10 +34,8 @@ static atomic<int32_t> rendererCnt{-1};
void WgRenderer::release()
{
// check for general context availability
if (!mContext.queue) return;
// dispose stored objects
disposeObjects();
// clear render data paint pools
@ -45,15 +43,14 @@ void WgRenderer::release()
mRenderDataPicturePool.release(mContext);
mRenderDataViewportPool.release(mContext);
mRenderDataEffectParamsPool.release(mContext);
WgMeshDataPool::gMeshDataPool->release(mContext);
// clear render storage pool
mRenderStoragePool.release(mContext);
// clear render pool
mRenderTargetPool.release(mContext);
// clear rendering tree stacks
mCompositorStack.clear();
mRenderStorageStack.clear();
mRenderStorageRoot.release(mContext);
mCompositorList.clear();
mRenderTargetStack.clear();
mRenderTargetRoot.release(mContext);
// release context handles
mCompositor.release(mContext);
@ -102,7 +99,6 @@ void WgRenderer::clearTargets()
bool WgRenderer::surfaceConfigure(WGPUSurface surface, WgContext& context, uint32_t width, uint32_t height)
{
// store target surface properties
this->surface = surface;
if (width == 0 || height == 0 || !surface) return false;
@ -135,33 +131,29 @@ bool WgRenderer::surfaceConfigure(WGPUSurface surface, WgContext& context, uint3
RenderData WgRenderer::prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
{
// get or create render data shape
auto renderDataShape = (WgRenderDataShape*)data;
if (!renderDataShape)
renderDataShape = mRenderDataShapePool.allocate(mContext);
auto renderDataShape = data ? (WgRenderDataShape*)data : mRenderDataShapePool.allocate(mContext);
// update geometry
if ((!data) || (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke))) {
renderDataShape->updateMeshes(mContext, rshape, transform, mBufferPool.pool);
renderDataShape->updateMeshes(rshape, transform, mBufferPool.pool);
}
// update paint settings
if ((!data) || (flags & (RenderUpdateFlag::Transform | RenderUpdateFlag::Blend))) {
renderDataShape->update(mContext, transform, mTargetSurface.cs, opacity);
renderDataShape->renderSettingsShape.update(mContext, transform, mTargetSurface.cs, opacity);
renderDataShape->renderSettingsStroke.update(mContext, transform, mTargetSurface.cs, opacity);
renderDataShape->fillRule = rshape.rule;
}
// setup fill settings
renderDataShape->viewport = mViewport;
renderDataShape->opacity = opacity;
if (flags & RenderUpdateFlag::Gradient && rshape.fill) renderDataShape->renderSettingsShape.updateFill(mContext, rshape.fill);
else if (flags & RenderUpdateFlag::Color) renderDataShape->renderSettingsShape.updateColor(mContext, rshape.color);
if (flags & RenderUpdateFlag::Gradient && rshape.fill) renderDataShape->renderSettingsShape.update(mContext, rshape.fill);
else if (flags & RenderUpdateFlag::Color) renderDataShape->renderSettingsShape.update(mContext, rshape.color);
if (rshape.stroke) {
if (flags & RenderUpdateFlag::GradientStroke && rshape.stroke->fill) renderDataShape->renderSettingsStroke.updateFill(mContext, rshape.stroke->fill);
else if (flags & RenderUpdateFlag::Stroke) renderDataShape->renderSettingsStroke.updateColor(mContext, rshape.stroke->color);
if (flags & RenderUpdateFlag::GradientStroke && rshape.stroke->fill) renderDataShape->renderSettingsStroke.update(mContext, rshape.stroke->fill);
else if (flags & RenderUpdateFlag::Stroke) renderDataShape->renderSettingsStroke.update(mContext, rshape.stroke->color);
}
// store clips data
renderDataShape->updateClips(clips);
return renderDataShape;
@ -170,16 +162,12 @@ RenderData WgRenderer::prepare(const RenderShape& rshape, RenderData data, const
RenderData WgRenderer::prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
// get or create render data shape
auto renderDataPicture = (WgRenderDataPicture*)data;
if (!renderDataPicture)
renderDataPicture = mRenderDataPicturePool.allocate(mContext);
auto renderDataPicture = data ? (WgRenderDataPicture*)data : mRenderDataPicturePool.allocate(mContext);
// update paint settings
renderDataPicture->viewport = mViewport;
renderDataPicture->opacity = opacity;
if (flags & (RenderUpdateFlag::Transform | RenderUpdateFlag::Blend)) {
renderDataPicture->update(mContext, transform, surface->cs, opacity);
renderDataPicture->renderSettings.update(mContext, transform, surface->cs, opacity);
}
// update image data
@ -187,58 +175,87 @@ RenderData WgRenderer::prepare(RenderSurface* surface, RenderData data, const Ma
renderDataPicture->updateSurface(mContext, surface);
}
// store clips data
renderDataPicture->updateClips(clips);
return renderDataPicture;
}
bool WgRenderer::preRender()
{
// push rot render storage to the render tree stack
assert(mRenderStorageStack.count == 0);
mRenderStorageStack.push(&mRenderStorageRoot);
// create command encoder for drawing
WGPUCommandEncoderDescriptor commandEncoderDesc{};
mCommandEncoder = wgpuDeviceCreateCommandEncoder(mContext.device, &commandEncoderDesc);
// start root render pass
mCompositor.beginRenderPass(mCommandEncoder, mRenderStorageStack.last(), true);
if (mContext.invalid()) return false;
mCompositor.reset(mContext);
assert(mRenderTargetStack.count == 0);
mRenderTargetStack.push(&mRenderTargetRoot);
// create root compose settings
WgCompose* compose = new WgCompose();
compose->aabb = { { 0, 0 }, { (int32_t)mTargetSurface.w, (int32_t)mTargetSurface.h } };
compose->blend = BlendMethod::Normal;
compose->method = MaskMethod::None;
compose->opacity = 255;
mCompositorList.push(compose);
// create root scene
WgSceneTask* sceneTask = new WgSceneTask(&mRenderTargetRoot, compose, nullptr);
mRenderTaskList.push(sceneTask);
mSceneTaskStack.push(sceneTask);
return true;
}
bool WgRenderer::renderShape(RenderData data)
{
// temporary simple render data to the current render target
mCompositor.renderShape(mContext, (WgRenderDataShape*)data, mBlendMethod);
WgPaintTask* paintTask = new WgPaintTask((WgRenderDataPaint*)data, mBlendMethod);
WgSceneTask* sceneTask = mSceneTaskStack.last();
sceneTask->children.push(paintTask);
mRenderTaskList.push(paintTask);
mCompositor.requestShape((WgRenderDataShape*)data);
return true;
}
bool WgRenderer::renderImage(RenderData data)
{
// temporary simple render data to the current render target
mCompositor.renderImage(mContext, (WgRenderDataPicture*)data, mBlendMethod);
WgPaintTask* paintTask = new WgPaintTask((WgRenderDataPaint*)data, mBlendMethod);
WgSceneTask* sceneTask = mSceneTaskStack.last();
sceneTask->children.push(paintTask);
mRenderTaskList.push(paintTask);
mCompositor.requestImage((WgRenderDataPicture*)data);
return true;
}
bool WgRenderer::postRender()
{
// end root render pass
mCompositor.endRenderPass();
// release command encoder
const WGPUCommandBufferDescriptor commandBufferDesc{};
WGPUCommandBuffer commandsBuffer = wgpuCommandEncoderFinish(mCommandEncoder, &commandBufferDesc);
wgpuQueueSubmit(mContext.queue, 1, &commandsBuffer);
wgpuCommandBufferRelease(commandsBuffer);
wgpuCommandEncoderRelease(mCommandEncoder);
// pop root render storage to the render tree stack
mRenderStorageStack.pop();
assert(mRenderStorageStack.count == 0);
// clear viewport list and store allocated handles to pool
ARRAY_FOREACH(p, mRenderDataViewportList)
// flush stage data to gpu
mCompositor.flush(mContext);
// create command encoder for drawing
WGPUCommandEncoder commandEncoder = mContext.createCommandEncoder();
// run rendering (all the fun is here)
WgSceneTask* sceneTaskRoot = mSceneTaskStack.last();
sceneTaskRoot->run(mContext, mCompositor, commandEncoder);
// execute and release command encoder
mContext.submitCommandEncoder(commandEncoder);
mContext.releaseCommandEncoder(commandEncoder);
// clear the render tasks tree
mSceneTaskStack.pop();
assert(mSceneTaskStack.count == 0);
mRenderTargetStack.pop();
assert(mRenderTargetStack.count == 0);
ARRAY_FOREACH(p, mRenderTaskList) { delete (*p); };
mRenderTaskList.clear();
ARRAY_FOREACH(p, mCompositorList) { delete (*p); };
mCompositorList.clear();
ARRAY_FOREACH(p, mRenderDataViewportList) {
mRenderDataViewportPool.free(mContext, *p);
}
mRenderDataViewportList.clear();
return true;
}
@ -257,14 +274,9 @@ RenderRegion WgRenderer::region(RenderData data)
if (renderData->type() == Type::Shape) {
auto& v1 = renderData->aabb.min;
auto& v2 = renderData->aabb.max;
RenderRegion renderRegion;
renderRegion.x = static_cast<int32_t>(nearbyint(v1.x));
renderRegion.y = static_cast<int32_t>(nearbyint(v1.y));
renderRegion.w = static_cast<int32_t>(nearbyint(v2.x)) - renderRegion.x;
renderRegion.h = static_cast<int32_t>(nearbyint(v2.y)) - renderRegion.y;
return renderRegion;
return {{int32_t(nearbyint(v1.x)), int32_t(nearbyint(v1.y))}, {int32_t(nearbyint(v2.x)), int32_t(nearbyint(v2.y))}};
}
return { 0, 0, (int32_t)mTargetSurface.w, (int32_t)mTargetSurface.h };
return {{0, 0}, {(int32_t)mTargetSurface.w, (int32_t)mTargetSurface.h}};
}
@ -301,6 +313,8 @@ const RenderSurface* WgRenderer::mainSurface()
bool WgRenderer::clear()
{
if (mContext.invalid()) return false;
//TODO: clear the current target buffer only if clear() is called
return true;
}
@ -308,6 +322,8 @@ bool WgRenderer::clear()
bool WgRenderer::sync()
{
if (mContext.invalid()) return false;
disposeObjects();
// if texture buffer used
@ -317,57 +333,45 @@ bool WgRenderer::sync()
wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture);
dstTexture = surfaceTexture.texture;
}
// there is no external dest buffer
if (!dstTexture) return false;
// get external dest buffer
// insure that surface and offscreen target have the same size
if ((wgpuTextureGetWidth(dstTexture) == mRenderTargetRoot.width) &&
(wgpuTextureGetHeight(dstTexture) == mRenderTargetRoot.height)) {
WGPUTextureView dstTextureView = mContext.createTextureView(dstTexture);
// create command encoder
const WGPUCommandEncoderDescriptor commandEncoderDesc{};
WGPUCommandEncoder commandEncoder = wgpuDeviceCreateCommandEncoder(mContext.device, &commandEncoderDesc);
WGPUCommandEncoder commandEncoder = mContext.createCommandEncoder();
// show root offscreen buffer
mCompositor.blit(mContext, commandEncoder, &mRenderStorageRoot, dstTextureView);
// release command encoder
const WGPUCommandBufferDescriptor commandBufferDesc{};
WGPUCommandBuffer commandsBuffer = wgpuCommandEncoderFinish(commandEncoder, &commandBufferDesc);
wgpuQueueSubmit(mContext.queue, 1, &commandsBuffer);
wgpuCommandBufferRelease(commandsBuffer);
wgpuCommandEncoderRelease(commandEncoder);
// release dest buffer view
mCompositor.blit(mContext, commandEncoder, &mRenderTargetRoot, dstTextureView);
mContext.submitCommandEncoder(commandEncoder);
mContext.releaseCommandEncoder(commandEncoder);
mContext.releaseTextureView(dstTextureView);
}
return true;
}
// render target handle
bool WgRenderer::target(WGPUDevice device, WGPUInstance instance, void* target, uint32_t width, uint32_t height, int type)
{
// release all existing handles
if (!instance || !device || !target) {
// release all handles
release();
return true;
}
// can not initialize renderer, give up
if (!instance || !device || !target || !width || !height)
return false;
if (!width || !height) return false;
// device or instance was changed, need to recreate all instances
if ((mContext.device != device) || (mContext.instance != instance)) {
// release all handles
release();
// initialize base rendering handles
mContext.initialize(instance, device);
// initialize render tree instances
mRenderStoragePool.initialize(mContext, width, height);
mRenderStorageRoot.initialize(mContext, width, height);
mRenderTargetPool.initialize(mContext, width, height);
mRenderTargetRoot.initialize(mContext, width, height);
mCompositor.initialize(mContext, width, height);
// store target properties
@ -386,12 +390,12 @@ bool WgRenderer::target(WGPUDevice device, WGPUInstance instance, void* target,
// update render targets dimentions
if ((mTargetSurface.w != width) || (mTargetSurface.h != height) || (type == 0 ? (surface != (WGPUSurface)target) : (targetTexture != (WGPUTexture)target))) {
// release render tagets
mRenderStoragePool.release(mContext);
mRenderStorageRoot.release(mContext);
mRenderTargetPool.release(mContext);
mRenderTargetRoot.release(mContext);
clearTargets();
mRenderStoragePool.initialize(mContext, width, height);
mRenderStorageRoot.initialize(mContext, width, height);
mRenderTargetPool.initialize(mContext, width, height);
mRenderTargetRoot.initialize(mContext, width, height);
mCompositor.resize(mContext, width, height);
// store target properties
@ -446,7 +450,7 @@ RenderCompositor* WgRenderer::target(const RenderRegion& region, TVG_UNUSED Colo
compose->rdViewport->update(mContext, region);
mRenderDataViewportList.push(compose->rdViewport);
}
mCompositorStack.push(compose);
mCompositorList.push(compose);
return compose;
}
@ -458,145 +462,101 @@ bool WgRenderer::beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_
compose->method = method;
compose->opacity = opacity;
compose->blend = mBlendMethod;
// end current render pass
mCompositor.endRenderPass();
// allocate new render storage and push to the render tree stack
WgRenderStorage* storage = mRenderStoragePool.allocate(mContext);
mRenderStorageStack.push(storage);
// begin newly added render pass
WGPUColor color{};
if ((compose->method == MaskMethod::None) && (compose->blend != BlendMethod::Normal)) color = { 1.0, 1.0, 1.0, 0.0 };
mCompositor.beginRenderPass(mCommandEncoder, mRenderStorageStack.last(), true, color);
WgSceneTask* sceneTaskCurrent = mSceneTaskStack.last();
// allocate new render target and push to the render tree stack
WgRenderTarget* renderTarget = mRenderTargetPool.allocate(mContext);
mRenderTargetStack.push(renderTarget);
// create and setup new scene task
WgSceneTask* sceneTask = new WgSceneTask(renderTarget, compose, sceneTaskCurrent);
sceneTaskCurrent->children.push(sceneTask);
mRenderTaskList.push(sceneTask);
mSceneTaskStack.push(sceneTask);
return true;
}
bool WgRenderer::endComposite(RenderCompositor* cmp)
{
// get current composition settings
WgCompose* comp = (WgCompose*)cmp;
// we must to end current render pass to run blend/composition mechanics
mCompositor.endRenderPass();
// finish scene blending
if (comp->method == MaskMethod::None) {
// get source and destination render storages
WgRenderStorage* src = mRenderStorageStack.last();
mRenderStorageStack.pop();
WgRenderStorage* dst = mRenderStorageStack.last();
// begin previous render pass
mCompositor.beginRenderPass(mCommandEncoder, dst, false);
// apply composition
mCompositor.renderScene(mContext, src, comp);
if (cmp->method == MaskMethod::None) {
// get source and destination render targets
WgRenderTarget* src = mRenderTargetStack.last();
mRenderTargetStack.pop();
WgSceneTask* srcScene = mSceneTaskStack.last();
mSceneTaskStack.pop();
// setup render target compose destitations
srcScene->renderTargetDst = mSceneTaskStack.last()->renderTarget;
srcScene->renderTargetMsk = nullptr;
// back render targets to the pool
mRenderStoragePool.free(mContext, src);
} else { // finish composition
// get source, mask and destination render storages
WgRenderStorage* src = mRenderStorageStack.last();
mRenderStorageStack.pop();
WgRenderStorage* msk = mRenderStorageStack.last();
mRenderStorageStack.pop();
WgRenderStorage* dst = mRenderStorageStack.last();
// begin previous render pass
mCompositor.beginRenderPass(mCommandEncoder, dst, false);
// apply composition
mCompositor.composeScene(mContext, src, msk, comp);
mRenderTargetPool.free(mContext, src);
} else { // finish scene composition
// get source, mask and destination render targets
WgRenderTarget* src = mRenderTargetStack.last();
mRenderTargetStack.pop();
WgRenderTarget* msk = mRenderTargetStack.last();
mRenderTargetStack.pop();
// get source and mask scenes
WgSceneTask* srcScene = mSceneTaskStack.last();
mSceneTaskStack.pop();
WgSceneTask* mskScene = mSceneTaskStack.last();
mSceneTaskStack.pop();
// setup render target compose destitations
srcScene->renderTargetDst = mSceneTaskStack.last()->renderTarget;
srcScene->renderTargetMsk = mskScene->renderTarget;
// back render targets to the pool
mRenderStoragePool.free(mContext, src);
mRenderStoragePool.free(mContext, msk);
mRenderTargetPool.free(mContext, src);
mRenderTargetPool.free(mContext, msk);
}
// delete current compositor settings
delete mCompositorStack.last();
mCompositorStack.pop();
return true;
}
void WgRenderer::prepare(RenderEffect* effect, const Matrix& transform)
{
// prepare gaussian blur data
if (!effect->rd) effect->rd = mRenderDataEffectParamsPool.allocate(mContext);
auto renderData = (WgRenderDataEffectParams*)effect->rd;
if (effect->type == SceneEffect::GaussianBlur) {
auto renderEffect = (RenderEffectGaussianBlur*)effect;
auto renderData = (WgRenderDataEffectParams*)renderEffect->rd;
if (!renderData) {
renderData = mRenderDataEffectParamsPool.allocate(mContext);
renderEffect->rd = renderData;
renderData->update(mContext, (RenderEffectGaussianBlur*)effect, transform);
} else if (effect->type == SceneEffect::DropShadow) {
renderData->update(mContext, (RenderEffectDropShadow*)effect, transform);
} else if (effect->type == SceneEffect::Fill) {
renderData->update(mContext, (RenderEffectFill*)effect);
} else if (effect->type == SceneEffect::Tint) {
renderData->update(mContext, (RenderEffectTint*)effect);
} else if (effect->type == SceneEffect::Tritone) {
renderData->update(mContext, (RenderEffectTritone*)effect);
} else {
TVGERR("WG_ENGINE", "Missing effect type? = %d", (int) effect->type);
return;
}
renderData->update(mContext, renderEffect, transform);
effect->valid = true;
} else
// prepare drop shadow data
if (effect->type == SceneEffect::DropShadow) {
auto renderEffect = (RenderEffectDropShadow*)effect;
auto renderData = (WgRenderDataEffectParams*)renderEffect->rd;
if (!renderData) {
renderData = mRenderDataEffectParamsPool.allocate(mContext);
renderEffect->rd = renderData;
}
renderData->update(mContext, renderEffect, transform);
effect->valid = true;
} else
// prepare fill
if (effect->type == SceneEffect::Fill) {
auto renderEffect = (RenderEffectFill*)effect;
auto renderData = (WgRenderDataEffectParams*)renderEffect->rd;
if (!renderData) {
renderData = mRenderDataEffectParamsPool.allocate(mContext);
renderEffect->rd = renderData;
}
renderData->update(mContext, renderEffect);
effect->valid = true;
} else
// prepare tint
if (effect->type == SceneEffect::Tint) {
auto renderEffect = (RenderEffectTint*)effect;
auto renderData = (WgRenderDataEffectParams*)renderEffect->rd;
if (!renderData) {
renderData = mRenderDataEffectParamsPool.allocate(mContext);
renderEffect->rd = renderData;
}
renderData->update(mContext, renderEffect);
effect->valid = true;
} else
// prepare tritone
if (effect->type == SceneEffect::Tritone) {
auto renderEffect = (RenderEffectTritone*)effect;
auto renderData = (WgRenderDataEffectParams*)renderEffect->rd;
if (!renderData) {
renderData = mRenderDataEffectParamsPool.allocate(mContext);
renderEffect->rd = renderData;
}
renderData->update(mContext, renderEffect);
effect->valid = true;
}
}
bool WgRenderer::region(RenderEffect* effect)
{
// update gaussian blur region
if (effect->type == SceneEffect::GaussianBlur) {
auto gaussian = (RenderEffectGaussianBlur*)effect;
auto renderData = (WgRenderDataEffectParams*)gaussian->rd;
if (gaussian->direction != 2) {
gaussian->extend.x = -renderData->extend;
gaussian->extend.w = +renderData->extend * 2;
gaussian->extend.min.x = -renderData->extend;
gaussian->extend.max.x = +renderData->extend;
}
if (gaussian->direction != 1) {
gaussian->extend.y = -renderData->extend;
gaussian->extend.h = +renderData->extend * 2;
gaussian->extend.min.y = -renderData->extend;
gaussian->extend.max.y = +renderData->extend;
}
return true;
} else
// update drop shadow region
if (effect->type == SceneEffect::DropShadow) {
} else if (effect->type == SceneEffect::DropShadow) {
auto dropShadow = (RenderEffectDropShadow*)effect;
auto renderData = (WgRenderDataEffectParams*)dropShadow->rd;
dropShadow->extend.x = -(renderData->extend + std::abs(renderData->offset.x));
dropShadow->extend.w = +(renderData->extend + std::abs(renderData->offset.x)) * 2;
dropShadow->extend.y = -(renderData->extend + std::abs(renderData->offset.y));
dropShadow->extend.h = +(renderData->extend + std::abs(renderData->offset.y)) * 2;
dropShadow->extend.min.x = -(renderData->extend + std::abs(renderData->offset.x));
dropShadow->extend.max.x = +(renderData->extend + std::abs(renderData->offset.x));
dropShadow->extend.min.y = -(renderData->extend + std::abs(renderData->offset.y));
dropShadow->extend.max.y = +(renderData->extend + std::abs(renderData->offset.y));
return true;
}
return false;
@ -605,20 +565,8 @@ bool WgRenderer::region(RenderEffect* effect)
bool WgRenderer::render(RenderCompositor* cmp, const RenderEffect* effect, TVG_UNUSED bool direct)
{
// we must to end current render pass to resolve ms texture before effect
mCompositor.endRenderPass();
WgCompose* comp = (WgCompose*)cmp;
WgRenderStorage* dst = mRenderStorageStack.last();
switch (effect->type) {
case SceneEffect::GaussianBlur: return mCompositor.gaussianBlur(mContext, dst, (RenderEffectGaussianBlur*)effect, comp);
case SceneEffect::DropShadow: return mCompositor.dropShadow(mContext, dst, (RenderEffectDropShadow*)effect, comp);
case SceneEffect::Fill: return mCompositor.fillEffect(mContext, dst, (RenderEffectFill*)effect, comp);
case SceneEffect::Tint: return mCompositor.tintEffect(mContext, dst, (RenderEffectTint*)effect, comp);
case SceneEffect::Tritone : return mCompositor.tritoneEffect(mContext, dst, (RenderEffectTritone*)effect, comp);
default: return false;
}
return false;
mSceneTaskStack.last()->effect = effect;
return true;
}
@ -632,6 +580,8 @@ void WgRenderer::dispose(RenderEffect* effect)
bool WgRenderer::preUpdate()
{
if (mContext.invalid()) return false;
return true;
}

View file

@ -23,7 +23,7 @@
#ifndef _TVG_WG_RENDERER_H_
#define _TVG_WG_RENDERER_H_
#include "tvgWgCompositor.h"
#include "tvgWgRenderTask.h"
class WgRenderer : public RenderMethod
{
@ -72,13 +72,15 @@ private:
bool surfaceConfigure(WGPUSurface surface, WgContext& context, uint32_t width, uint32_t height);
// render tree stacks
WgRenderStorage mRenderStorageRoot;
Array<WgCompose*> mCompositorStack;
Array<WgRenderStorage*> mRenderStorageStack;
WgRenderTarget mRenderTargetRoot;
Array<WgCompose*> mCompositorList;
Array<WgRenderTarget*> mRenderTargetStack;
Array<WgRenderDataViewport*> mRenderDataViewportList;
Array<WgSceneTask*> mSceneTaskStack;
Array<WgRenderTask*> mRenderTaskList;
// render storage pool
WgRenderStoragePool mRenderStoragePool;
// render target pool
WgRenderTargetPool mRenderTargetPool;
// render data paint pools
WgRenderDataShapePool mRenderDataShapePool;
@ -100,7 +102,6 @@ private:
Key mDisposeKey{};
// gpu handles
WGPUCommandEncoder mCommandEncoder{};
WGPUTexture targetTexture{}; // external handle
WGPUSurfaceTexture surfaceTexture{};
WGPUSurface surface{}; // external handle

View file

@ -23,8 +23,6 @@
#include "tvgWgShaderSrc.h"
#include <string>
#define WG_SHADER_SOURCE(...) #__VA_ARGS__
//************************************************************************
// graphics shader source: stencil
//************************************************************************
@ -32,14 +30,15 @@
const char* cShaderSrc_Stencil = R"(
struct VertexInput { @location(0) position: vec2f };
struct VertexOutput { @builtin(position) position: vec4f };
struct PaintSettings { transform: mat4x4f };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
out.position = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
return out;
}
@ -56,15 +55,16 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
const char* cShaderSrc_Depth = R"(
struct VertexInput { @location(0) position: vec2f };
struct VertexOutput { @builtin(position) position: vec4f };
struct PaintSettings { transform: mat4x4f };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@group(2) @binding(0) var<uniform> uDepth : f32;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
out.position = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
out.position.z = uDepth;
return out;
}
@ -82,23 +82,22 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
const char* cShaderSrc_Solid = R"(
struct VertexInput { @location(0) position: vec2f };
struct VertexOutput { @builtin(position) position: vec4f };
struct PaintSettings { transform: mat4x4f, options: vec4f, color: vec4f };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(1) var<uniform> uBlendSettings : vec4f;
@group(2) @binding(0) var<uniform> uSolidColor : vec4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
out.position = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let Sc = uSolidColor;
let So = uBlendSettings.a;
let Sc = uPaintSettings.color;
let So = uPaintSettings.options.a;
return vec4f(Sc.rgb * Sc.a * So, Sc.a * So);
}
)";
@ -110,34 +109,32 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
const char* cShaderSrc_Linear = R"(
struct VertexInput { @location(0) position: vec2f };
struct VertexOutput { @builtin(position) position : vec4f, @location(0) vGradCoord : vec4f };
struct GradSettings { settings: vec4f, focal: vec4f };
struct GradSettings { transform: mat4x4f, coords: vec4f, focal: vec4f };
struct PaintSettings { transform: mat4x4f, options: vec4f, color: vec4f, gradient: GradSettings };
// uniforms
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(1) var<uniform> uBlendSettings : vec4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@group(2) @binding(0) var uSamplerGrad : sampler;
@group(2) @binding(1) var uTextureGrad : texture_2d<f32>;
@group(2) @binding(2) var<uniform> uSettingGrad : GradSettings;
@group(2) @binding(3) var<uniform> uTransformGrad : mat4x4f;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
out.vGradCoord = uTransformGrad * vec4f(in.position.xy, 0.0, 1.0);
out.position = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
out.vGradCoord = uPaintSettings.gradient.transform * vec4f(in.position.xy, 0.0, 1.0);
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let pos = in.vGradCoord.xy;
let st = uSettingGrad.settings.xy;
let ed = uSettingGrad.settings.zw;
let st = uPaintSettings.gradient.coords.xy;
let ed = uPaintSettings.gradient.coords.zw;
let ba = ed - st;
let t = dot(pos - st, ba) / dot(ba, ba);
let Sc = textureSample(uTextureGrad, uSamplerGrad, vec2f(t, 0.5));
let So = uBlendSettings.a;
let So = uPaintSettings.options.a;
return vec4f(Sc.rgb * Sc.a * So, Sc.a * So);
}
)";
@ -149,37 +146,35 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
const char* cShaderSrc_Radial = R"(
struct VertexInput { @location(0) position: vec2f };
struct VertexOutput { @builtin(position) position : vec4f, @location(0) vGradCoord : vec4f };
struct GradSettings { settings: vec4f, focal: vec4f };
struct GradSettings { transform: mat4x4f, coords: vec4f, focal: vec4f };
struct PaintSettings { transform: mat4x4f, options: vec4f, color: vec4f, gradient: GradSettings };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(1) var<uniform> uBlendSettings : vec4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@group(2) @binding(0) var uSamplerGrad : sampler;
@group(2) @binding(1) var uTextureGrad : texture_2d<f32>;
@group(2) @binding(2) var<uniform> uSettingGrad : GradSettings;
@group(2) @binding(3) var<uniform> uTransformGrad : mat4x4f;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
out.vGradCoord = uTransformGrad * vec4f(in.position.xy, 0.0, 1.0);
out.position = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
out.vGradCoord = uPaintSettings.gradient.transform * vec4f(in.position.xy, 0.0, 1.0);
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
// orignal data
let d0 = in.vGradCoord.xy - uSettingGrad.settings.xy;
let d1 = uSettingGrad.settings.xy - uSettingGrad.focal.xy;
let r0 = uSettingGrad.settings.z;
let rd = uSettingGrad.focal.z - uSettingGrad.settings.z;
let d0 = in.vGradCoord.xy - uPaintSettings.gradient.coords.xy;
let d1 = uPaintSettings.gradient.coords.xy - uPaintSettings.gradient.focal.xy;
let r0 = uPaintSettings.gradient.coords.z;
let rd = uPaintSettings.gradient.focal.z - uPaintSettings.gradient.coords.z;
let a = 1.0*dot(d1, d1) - 1.0*rd*rd;
let b = 2.0*dot(d0, d1) - 2.0*r0*rd;
let c = 1.0*dot(d0, d0) - 1.0*r0*r0;
let t = (-b + sqrt(b*b - 4*a*c))/(2*a);
let Sc = textureSample(uTextureGrad, uSamplerGrad, vec2f(1.0 - t, 0.5));
let So = uBlendSettings.a;
let So = uPaintSettings.options.a;
return vec4f(Sc.rgb * Sc.a * So, Sc.a * So);
}
)";
@ -191,17 +186,17 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
const char* cShaderSrc_Image = R"(
struct VertexInput { @location(0) position: vec2f, @location(1) texCoord: vec2f };
struct VertexOutput { @builtin(position) position: vec4f, @location(0) vTexCoord: vec2f };
struct PaintSettings { transform: mat4x4f, options: vec4f };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(1) var<uniform> uBlendSettings : vec4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@group(2) @binding(0) var uSampler : sampler;
@group(2) @binding(1) var uTextureView : texture_2d<f32>;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
out.position = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
out.vTexCoord = in.texCoord;
return out;
}
@ -209,7 +204,7 @@ fn vs_main(in: VertexInput) -> VertexOutput {
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
var Sc: vec4f = textureSample(uTextureView, uSampler, in.vTexCoord.xy);
let So: f32 = uBlendSettings.a;
let So: f32 = uPaintSettings.options.a;
return vec4f(Sc.rgb * Sc.a * So, Sc.a * So);
};
)";
@ -248,18 +243,18 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
const char* cShaderSrc_Solid_Blend = R"(
struct VertexInput { @location(0) position: vec2f };
struct VertexOutput { @builtin(position) position: vec4f, @location(1) vScrCoord: vec2f };
struct PaintSettings { transform: mat4x4f, options: vec4f, color: vec4f };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(1) var<uniform> uBlendSettings : vec4f;
@group(2) @binding(0) var<uniform> uSolidColor : vec4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
// @group(2) - empty
@group(3) @binding(0) var uSamplerDst : sampler;
@group(3) @binding(1) var uTextureDst : texture_2d<f32>;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let pos = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
let pos = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
out.position = pos;
out.vScrCoord = vec2f(0.5 + pos.x * 0.5, 0.5 - pos.y * 0.5);
return out;
@ -268,13 +263,13 @@ fn vs_main(in: VertexInput) -> VertexOutput {
struct FragData { Sc: vec3f, Sa: f32, So: f32, Dc: vec3f, Da: f32 };
fn getFragData(in: VertexOutput) -> FragData {
// get source data
let colorSrc = uSolidColor;
let colorSrc = uPaintSettings.color;
let colorDst = textureSample(uTextureDst, uSamplerDst, in.vScrCoord.xy);
// fill fragment data
var data: FragData;
data.Sc = colorSrc.rgb;
data.Sa = colorSrc.a;
data.So = uBlendSettings.a;
data.So = uPaintSettings.options.a;
data.Dc = colorDst.rgb;
data.Da = colorDst.a;
data.Sc = data.Sa * data.So * data.Sc;
@ -288,24 +283,22 @@ fn postProcess(d: FragData, R: vec4f) -> vec4f { return R; };
const char* cShaderSrc_Linear_Blend = R"(
struct VertexInput { @location(0) position: vec2f };
struct VertexOutput { @builtin(position) position: vec4f, @location(0) vGradCoord : vec4f, @location(1) vScrCoord: vec2f };
struct GradSettings { settings: vec4f, focal: vec4f };
struct GradSettings { transform: mat4x4f, coords: vec4f, focal: vec4f };
struct PaintSettings { transform: mat4x4f, options: vec4f, color: vec4f, gradient: GradSettings };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(1) var<uniform> uBlendSettings : vec4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@group(2) @binding(0) var uSamplerGrad : sampler;
@group(2) @binding(1) var uTextureGrad : texture_2d<f32>;
@group(2) @binding(2) var<uniform> uSettingGrad : vec4f;
@group(2) @binding(3) var<uniform> uTransformGrad : mat4x4f;
@group(3) @binding(0) var uSamplerDst : sampler;
@group(3) @binding(1) var uTextureDst : texture_2d<f32>;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let pos = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
let pos = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
out.position = pos;
out.vGradCoord = uTransformGrad * vec4f(in.position.xy, 0.0, 1.0);
out.vGradCoord = uPaintSettings.gradient.transform * vec4f(in.position.xy, 0.0, 1.0);
out.vScrCoord = vec2f(0.5 + pos.x * 0.5, 0.5 - pos.y * 0.5);
return out;
}
@ -314,8 +307,8 @@ struct FragData { Sc: vec3f, Sa: f32, So: f32, Dc: vec3f, Da: f32 };
fn getFragData(in: VertexOutput) -> FragData {
// get source data
let pos = in.vGradCoord.xy;
let st = uSettingGrad.xy;
let ed = uSettingGrad.zw;
let st = uPaintSettings.gradient.coords.xy;
let ed = uPaintSettings.gradient.coords.zw;
let ba = ed - st;
let t = dot(pos - st, ba) / dot(ba, ba);
let colorSrc = textureSample(uTextureGrad, uSamplerGrad, vec2f(t, 0.5));
@ -324,7 +317,7 @@ fn getFragData(in: VertexOutput) -> FragData {
var data: FragData;
data.Sc = colorSrc.rgb;
data.Sa = colorSrc.a;
data.So = uBlendSettings.a;
data.So = uPaintSettings.options.a;
data.Dc = colorDst.rgb;
data.Da = colorDst.a;
data.Sc = mix(data.Dc, data.Sc, data.Sa * data.So);
@ -338,34 +331,32 @@ fn postProcess(d: FragData, R: vec4f) -> vec4f { return R; };
const char* cShaderSrc_Radial_Blend = R"(
struct VertexInput { @location(0) position: vec2f };
struct VertexOutput { @builtin(position) position: vec4f, @location(0) vGradCoord : vec4f, @location(1) vScrCoord: vec2f };
struct GradSettings { settings: vec4f, focal: vec4f };
struct GradSettings { transform: mat4x4f, coords: vec4f, focal: vec4f };
struct PaintSettings { transform: mat4x4f, options: vec4f, color: vec4f, gradient: GradSettings };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(1) var<uniform> uBlendSettings : vec4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@group(2) @binding(0) var uSamplerGrad : sampler;
@group(2) @binding(1) var uTextureGrad : texture_2d<f32>;
@group(2) @binding(2) var<uniform> uSettingGrad : GradSettings;
@group(2) @binding(3) var<uniform> uTransformGrad : mat4x4f;
@group(3) @binding(0) var uSamplerDst : sampler;
@group(3) @binding(1) var uTextureDst : texture_2d<f32>;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let pos = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
let pos = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
out.position = pos;
out.vGradCoord = uTransformGrad * vec4f(in.position.xy, 0.0, 1.0);
out.vGradCoord = uPaintSettings.gradient.transform * vec4f(in.position.xy, 0.0, 1.0);
out.vScrCoord = vec2f(0.5 + pos.x * 0.5, 0.5 - pos.y * 0.5);
return out;
}
struct FragData { Sc: vec3f, Sa: f32, So: f32, Dc: vec3f, Da: f32 };
fn getFragData(in: VertexOutput) -> FragData {
let d0 = in.vGradCoord.xy - uSettingGrad.settings.xy;
let d1 = uSettingGrad.settings.xy - uSettingGrad.focal.xy;
let r0 = uSettingGrad.settings.z;
let rd = uSettingGrad.focal.z - uSettingGrad.settings.z;
let d0 = in.vGradCoord.xy - uPaintSettings.gradient.coords.xy;
let d1 = uPaintSettings.gradient.coords.xy - uPaintSettings.gradient.focal.xy;
let r0 = uPaintSettings.gradient.coords.z;
let rd = uPaintSettings.gradient.focal.z - uPaintSettings.gradient.coords.z;
let a = 1.0*dot(d1, d1) - 1.0*rd*rd;
let b = 2.0*dot(d0, d1) - 2.0*r0*rd;
let c = 1.0*dot(d0, d0) - 1.0*r0*r0;
@ -376,7 +367,7 @@ fn getFragData(in: VertexOutput) -> FragData {
var data: FragData;
data.Sc = colorSrc.rgb;
data.Sa = colorSrc.a;
data.So = uBlendSettings.a;
data.So = uPaintSettings.options.a;
data.Dc = colorDst.rgb;
data.Da = colorDst.a;
data.Sc = mix(data.Dc, data.Sc, data.Sa * data.So);
@ -390,10 +381,10 @@ fn postProcess(d: FragData, R: vec4f) -> vec4f { return R; };
const char* cShaderSrc_Image_Blend = R"(
struct VertexInput { @location(0) position: vec2f, @location(1) texCoord: vec2f };
struct VertexOutput { @builtin(position) position: vec4f, @location(0) vTexCoord : vec2f, @location(1) vScrCoord: vec2f };
struct PaintSettings { transform: mat4x4f, options: vec4f };
@group(0) @binding(0) var<uniform> uViewMat : mat4x4f;
@group(1) @binding(0) var<uniform> uModelMat : mat4x4f;
@group(1) @binding(1) var<uniform> uBlendSettings : vec4f;
@group(1) @binding(0) var<uniform> uPaintSettings : PaintSettings;
@group(2) @binding(0) var uSamplerSrc : sampler;
@group(2) @binding(1) var uTextureSrc : texture_2d<f32>;
@group(3) @binding(0) var uSamplerDst : sampler;
@ -402,7 +393,7 @@ struct VertexOutput { @builtin(position) position: vec4f, @location(0) vTexCoord
@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let pos = uViewMat * uModelMat * vec4f(in.position.xy, 0.0, 1.0);
let pos = uViewMat * uPaintSettings.transform * vec4f(in.position.xy, 0.0, 1.0);
out.position = pos;
out.vTexCoord = in.texCoord;
out.vScrCoord = vec2f(0.5 + pos.x * 0.5, 0.5 - pos.y * 0.5);
@ -418,7 +409,7 @@ fn getFragData(in: VertexOutput) -> FragData {
var data: FragData;
data.Sc = colorSrc.rgb;
data.Sa = colorSrc.a;
data.So = uBlendSettings.a;
data.So = uPaintSettings.options.a;
data.Dc = colorDst.rgb;
data.Da = colorDst.a;
data.Sc = data.Sc * data.So;

View file

@ -118,42 +118,41 @@ void WgShaderTypeVec4f::update(const RenderColor& c)
void WgShaderTypeVec4f::update(const RenderRegion& r)
{
vec[0] = r.x; // left
vec[1] = r.y; // top
vec[2] = r.x + r.w - 1; // right
vec[3] = r.y + r.h - 1; // bottom
vec[0] = r.min.x;
vec[1] = r.min.y;
vec[2] = r.max.x - 1;
vec[3] = r.max.y - 1;
}
//************************************************************************
// WgShaderTypeGradient
// WgShaderTypeGradSettings
//************************************************************************
void WgShaderTypeGradient::update(const LinearGradient* linearGradient)
void WgShaderTypeGradSettings::update(const Fill* fill)
{
// update gradient data
assert(fill);
// update transform matrix
Matrix invTransform;
if (inverse(&fill->transform(), &invTransform))
transform.update(invTransform);
else transform.identity();
// update gradient base points
if (fill->type() == Type::LinearGradient)
((LinearGradient*)fill)->linear(&coords.vec[0], &coords.vec[1], &coords.vec[2], &coords.vec[3]);
else if (fill->type() == Type::RadialGradient)
((RadialGradient*)fill)->radial(&coords.vec[0], &coords.vec[1], &coords.vec[2], &focal.vec[0], &focal.vec[1], &focal.vec[2]);
}
//************************************************************************
// WgShaderTypeGradientData
//************************************************************************
void WgShaderTypeGradientData::update(const Fill* fill)
{
if (!fill) return;
const Fill::ColorStop* stops = nullptr;
auto stopCnt = linearGradient->colorStops(&stops);
updateTexData(stops, stopCnt);
// update base points
linearGradient->linear(&settings[0], &settings[1], &settings[2], &settings[3]);
};
void WgShaderTypeGradient::update(const RadialGradient* radialGradient)
{
// update gradient data
const Fill::ColorStop* stops = nullptr;
auto stopCnt = radialGradient->colorStops(&stops);
updateTexData(stops, stopCnt);
// update base points
radialGradient->radial(&settings[0], &settings[1], &settings[2], &settings[4], &settings[5], &settings[6]);
};
void WgShaderTypeGradient::updateTexData(const Fill::ColorStop* stops, uint32_t stopCnt)
{
auto stopCnt = fill->colorStops(&stops);
if (stopCnt == 0) return;
static Array<Fill::ColorStop> sstops(stopCnt);
sstops.clear();
sstops.push(stops[0]);
@ -167,10 +166,10 @@ void WgShaderTypeGradient::updateTexData(const Fill::ColorStop* stops, uint32_t
uint32_t range_s = 0;
uint32_t range_e = uint32_t(sstops[0].offset * (WG_TEXTURE_GRADIENT_SIZE-1));
for (uint32_t ti = range_s; (ti < range_e) && (ti < WG_TEXTURE_GRADIENT_SIZE); ti++) {
texData[ti * 4 + 0] = sstops[0].r;
texData[ti * 4 + 1] = sstops[0].g;
texData[ti * 4 + 2] = sstops[0].b;
texData[ti * 4 + 3] = sstops[0].a;
data[ti * 4 + 0] = sstops[0].r;
data[ti * 4 + 1] = sstops[0].g;
data[ti * 4 + 2] = sstops[0].b;
data[ti * 4 + 3] = sstops[0].a;
}
// body
for (uint32_t di = 1; di < sstops.count; di++) {
@ -179,10 +178,10 @@ void WgShaderTypeGradient::updateTexData(const Fill::ColorStop* stops, uint32_t
float delta = 1.0f/(range_e - range_s);
for (uint32_t ti = range_s; (ti < range_e) && (ti < WG_TEXTURE_GRADIENT_SIZE); ti++) {
float t = (ti - range_s) * delta;
texData[ti * 4 + 0] = tvg::lerp(sstops[di-1].r, sstops[di].r, t);
texData[ti * 4 + 1] = tvg::lerp(sstops[di-1].g, sstops[di].g, t);
texData[ti * 4 + 2] = tvg::lerp(sstops[di-1].b, sstops[di].b, t);
texData[ti * 4 + 3] = tvg::lerp(sstops[di-1].a, sstops[di].a, t);
data[ti * 4 + 0] = tvg::lerp(sstops[di-1].r, sstops[di].r, t);
data[ti * 4 + 1] = tvg::lerp(sstops[di-1].g, sstops[di].g, t);
data[ti * 4 + 2] = tvg::lerp(sstops[di-1].b, sstops[di].b, t);
data[ti * 4 + 3] = tvg::lerp(sstops[di-1].a, sstops[di].a, t);
}
}
// tail
@ -190,10 +189,10 @@ void WgShaderTypeGradient::updateTexData(const Fill::ColorStop* stops, uint32_t
range_s = uint32_t(colorStopLast.offset * (WG_TEXTURE_GRADIENT_SIZE-1));
range_e = WG_TEXTURE_GRADIENT_SIZE;
for (uint32_t ti = range_s; ti < range_e; ti++) {
texData[ti * 4 + 0] = colorStopLast.r;
texData[ti * 4 + 1] = colorStopLast.g;
texData[ti * 4 + 2] = colorStopLast.b;
texData[ti * 4 + 3] = colorStopLast.a;
data[ti * 4 + 0] = colorStopLast.r;
data[ti * 4 + 1] = colorStopLast.g;
data[ti * 4 + 2] = colorStopLast.b;
data[ti * 4 + 3] = colorStopLast.a;
}
}

View file

@ -55,16 +55,44 @@ struct WgShaderTypeVec4f
void update(const RenderRegion& r);
};
// sampler, texture, vec4f
#define WG_TEXTURE_GRADIENT_SIZE 512
struct WgShaderTypeGradient
// WGSL: struct GradSettings { transform: mat4x4f, coords: vec4f, focal: vec4f };
struct WgShaderTypeGradSettings
{
float settings[4+4]{}; // WGSL: struct GradSettings { settings: vec4f, focal: vec4f; transform: mat4f };
uint8_t texData[WG_TEXTURE_GRADIENT_SIZE * 4];
// gradient transform matrix
WgShaderTypeMat4x4f transform;
// linear: [0] - x1, [1] - y1, [2] - x2, [3] - y2
// radial: [0] - cx, [1] - cy, [2] - cr
WgShaderTypeVec4f coords;
// radial: [0] - fx, [1] - fy, [2] - fr
WgShaderTypeVec4f focal;
void update(const LinearGradient* linearGradient);
void update(const RadialGradient* radialGradient);
void updateTexData(const Fill::ColorStop* stops, uint32_t stopCnt);
void update(const Fill* fill);
};
// WGSL: struct PaintSettings { transform: mat4x4f, options: vec4f, color: vec4f, gradient: GradSettings };
struct WgShaderTypePaintSettings
{
// paint transform matrix (must be at offset 0)
WgShaderTypeMat4x4f transform;
// [0] - color space, [3] - opacity
WgShaderTypeVec4f options;
// solid color
WgShaderTypeVec4f color;
// gradient settings (linear/radial)
WgShaderTypeGradSettings gradient;
// align to 256 bytes (see webgpu spec: minUniformBufferOffsetAlignment)
uint8_t _padding[256 - sizeof(transform) - sizeof(options) - sizeof(color) - sizeof(gradient)];
};
// see webgpu spec: 3.6.2. Limits - minUniformBufferOffsetAlignment (256)
static_assert(sizeof(WgShaderTypePaintSettings) == 256, "Uniform shader data type size must be aligned to 256 bytes");
// gradient color map
#define WG_TEXTURE_GRADIENT_SIZE 512
struct WgShaderTypeGradientData
{
uint8_t data[WG_TEXTURE_GRADIENT_SIZE * 4];
void update(const Fill* fill);
};
// gaussian params: sigma, scale, extend

View file

@ -121,6 +121,9 @@ TEST_CASE("Bounding Box", "[tvgPaint]")
Initializer::init(0);
auto canvas = unique_ptr<SwCanvas>(SwCanvas::gen());
uint32_t buffer[100*100];
canvas->target(buffer, 100, 100, 100, ColorSpace::ABGR8888);
auto shape = Shape::gen();
canvas->push(shape);
canvas->sync();
@ -220,7 +223,9 @@ TEST_CASE("Composition", "[tvgPaint]")
//Clipping
auto comp = Shape::gen();
REQUIRE(shape->clip() == nullptr);
REQUIRE(shape->clip(comp) == Result::Success);
REQUIRE(shape->clip() == comp);
//AlphaMask
comp = Shape::gen();

View file

@ -80,7 +80,8 @@ TEST_CASE("Scene Clear And Reuse Shape", "[tvgScene]")
REQUIRE(Initializer::init(0) == Result::Success);
{
auto canvas = unique_ptr<SwCanvas>(SwCanvas::gen());
REQUIRE(canvas);
uint32_t buffer[100*100];
canvas->target(buffer, 100, 100, 100, ColorSpace::ABGR8888);
auto scene = Scene::gen();
REQUIRE(scene);

View file

@ -120,6 +120,8 @@ TEST_CASE("Text Basic", "[tvgText]")
Initializer::init(0);
auto canvas = unique_ptr<SwCanvas>(SwCanvas::gen());
uint32_t buffer[100*100];
canvas->target(buffer, 100, 100, 100, ColorSpace::ABGR8888);
auto text = Text::gen();
REQUIRE(text);
@ -144,6 +146,8 @@ TEST_CASE("Text with composite glyphs", "[tvgText]")
Initializer::init(0);
auto canvas = unique_ptr<SwCanvas>(SwCanvas::gen());
uint32_t buffer[100*100];
canvas->target(buffer, 100, 100, 100, ColorSpace::ABGR8888);
auto text = Text::gen();
REQUIRE(text);

View file

@ -54,7 +54,7 @@ private:
void helpMsg()
{
cout << "Usage: \n tvg-lotie2gif [Lottie file] or [Lottie folder] [-r resolution] [-f fps] [-b background color]\n\nExamples: \n $ tvg-lotie2gif input.json\n $ tvg-lotie2gif input.json -r 600x600\n $ tvg-lotie2gif input.json -f 30\n $ tvg-lotie2gif input.json -r 600x600 -f 30\n $ tvg-lotie2gif lottiefolder\n $ tvg-lotie2gif lottiefolder -r 600x600 -f 30 -b fa7410\n\n";
cout << "Usage: \n tvg-lottie2gif [Lottie file] or [Lottie folder] [-r resolution] [-f fps] [-b background color]\n\nExamples: \n $ tvg-lottie2gif input.json\n $ tvg-lottie2gif input.json -r 600x600\n $ tvg-lottie2gif input.json -f 30\n $ tvg-lottie2gif input.json -r 600x600 -f 30\n $ tvg-lottie2gif lottiefolder\n $ tvg-lottie2gif lottiefolder -r 600x600 -f 30 -b fa7410\n\n";
}
bool validate(string& lottieName)