iOS images unpremult SIMD support

Support for SIMD instructions for unpremult

First checkin, using rive::int16x4 instructions : 1 pixel at a time
Further checkin, using rive::int16x4 instructions : 2 pixels at a time
Last checkin, avoid computation when opaque pixels (assume there will be enough opaque pixels to warrant this)

Thanks to Chris for the SIMD instructions usage in rive

More checkins: move the decode and unpremult to the rive decoder - this requires modifications to build files. The benefits are we are now running tests on this path. However, there are some issues with decoding two images for tests:

"../../test/assets/bad.jpg" ... Apple Preview app cannot open this image, however, the current test says that it should be not null
And
"../../test/assets/bad.png", Apple Preview app can load this images, however, the current test says that it should be null

Diffs=
e992059d6 iOS images unpremult SIMD support (#7875)

Co-authored-by: rivessamr <suki@rive.app>
This commit is contained in:
rivessamr
2024-08-21 18:34:45 +00:00
parent 38404a22c7
commit 866f2311a7
8 changed files with 38 additions and 164 deletions

View File

@@ -1 +1 @@
ad34dd4dae54aa071ca80c457e375c015b9497a8
e992059d6354434e91cde562e463f51bff7eac58

View File

@@ -1 +1 @@
316bf61a8cc8299a3afc95ae9d5d255dd3281436
af95cf4b90c3d5633b35eb6bdb0619a7180bd8b6

View File

@@ -71,8 +71,6 @@
2E54F23A2BE428990013016C /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 2E54F2392BE428990013016C /* PrivacyInfo.xcprivacy */; };
83DE4C912AA8DD7B00B88B72 /* RenderContextManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83DE4C902AA8DD7B00B88B72 /* RenderContextManager.mm */; };
83DE4C932AA8DD9F00B88B72 /* RenderContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 83DE4C922AA8DD9F00B88B72 /* RenderContextManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
83DE4CA02AAA072B00B88B72 /* PlatformCGImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83DE4C9F2AAA072B00B88B72 /* PlatformCGImage.mm */; };
83DE4CA22AAA077200B88B72 /* PlatformCGImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 83DE4CA12AAA077200B88B72 /* PlatformCGImage.h */; };
83DE4CA72AAAE72100B88B72 /* RenderContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 83DE4CA62AAAE72000B88B72 /* RenderContext.h */; };
C34609FC27FF9114002DBCB7 /* RiveFile+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34609FB27FF9114002DBCB7 /* RiveFile+Extensions.swift */; };
C3468E5827EB9887008652FD /* RiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3468E5727EB9887008652FD /* RiveView.swift */; };
@@ -172,8 +170,6 @@
2E54F2392BE428990013016C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
83DE4C902AA8DD7B00B88B72 /* RenderContextManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RenderContextManager.mm; path = Source/Renderer/RenderContextManager.mm; sourceTree = SOURCE_ROOT; };
83DE4C922AA8DD9F00B88B72 /* RenderContextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderContextManager.h; sourceTree = "<group>"; };
83DE4C9F2AAA072B00B88B72 /* PlatformCGImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformCGImage.mm; sourceTree = "<group>"; };
83DE4CA12AAA077200B88B72 /* PlatformCGImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlatformCGImage.h; sourceTree = "<group>"; };
83DE4CA62AAAE72000B88B72 /* RenderContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderContext.h; sourceTree = "<group>"; };
C34609FB27FF9114002DBCB7 /* RiveFile+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RiveFile+Extensions.swift"; sourceTree = "<group>"; };
C3468E5727EB9887008652FD /* RiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveView.swift; sourceTree = "<group>"; };
@@ -230,7 +226,6 @@
046FB801264EB632000129B1 /* include */ = {
isa = PBXGroup;
children = (
83DE4CA12AAA077200B88B72 /* PlatformCGImage.h */,
C9C741F224FC510200EF9516 /* Rive.h */,
046FB7E7264EAA5F000129B1 /* RiveFile.h */,
046FB7EC264EAA60000129B1 /* RiveArtboard.h */,
@@ -317,7 +312,6 @@
isa = PBXGroup;
children = (
046FB801264EB632000129B1 /* include */,
83DE4C9F2AAA072B00B88B72 /* PlatformCGImage.mm */,
C9C741F324FC510200EF9516 /* Rive.mm */,
046FB7EF264EAA60000129B1 /* RiveFile.mm */,
046FB7E2264EAA5E000129B1 /* RiveArtboard.mm */,
@@ -413,7 +407,6 @@
046FB7F7264EAA60000129B1 /* RiveFile.h in Headers */,
046FB7FC264EAA61000129B1 /* RiveArtboard.h in Headers */,
2A7079352726277C00C035A1 /* rive_renderer_view.hh in Headers */,
83DE4CA22AAA077200B88B72 /* PlatformCGImage.h in Headers */,
83DE4C932AA8DD9F00B88B72 /* RenderContextManager.h in Headers */,
046FB800264EAA61000129B1 /* RiveStateMachineInstance.h in Headers */,
043025FA2AFA860E00320F2E /* FileAssetLoaderAdapter.hpp in Headers */,
@@ -557,7 +550,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
83DE4CA02AAA072B00B88B72 /* PlatformCGImage.mm in Sources */,
2A707937272628AD00C035A1 /* rive_renderer_view.mm in Sources */,
C34609FC27FF9114002DBCB7 /* RiveFile+Extensions.swift in Sources */,
C3468E5827EB9887008652FD /* RiveView.swift in Sources */,
@@ -800,6 +792,7 @@
"-lrive_yoga",
"-lrive_pls_renderer",
"-lrive_cg_renderer",
"-lrive_decoders",
);
"OTHER_LDFLAGS[sdk=iphonesimulator*]" = (
"-lrive_sim",
@@ -808,6 +801,7 @@
"-lrive_yoga_sim",
"-lrive_pls_renderer_sim",
"-lrive_cg_renderer_sim",
"-lrive_decoders_sim",
);
"OTHER_LDFLAGS[sdk=macosx*]" = (
"-lrive_macos",
@@ -816,6 +810,7 @@
"-lrive_yoga_macos",
"-lrive_pls_renderer_macos",
"-lrive_cg_renderer_macos",
"-lrive_decoders_macos",
);
PRODUCT_BUNDLE_IDENTIFIER = rive.app.ios.runtime.RiveRuntime;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
@@ -885,6 +880,7 @@
"-lrive_yoga",
"-lrive_pls_renderer",
"-lrive_cg_renderer",
"-lrive_decoders",
);
"OTHER_LDFLAGS[sdk=iphonesimulator*]" = (
"-lrive_sim",
@@ -893,6 +889,7 @@
"-lrive_yoga_sim",
"-lrive_pls_renderer_sim",
"-lrive_cg_renderer_sim",
"-lrive_decoders_sim",
);
"OTHER_LDFLAGS[sdk=macosx*]" = (
"-lrive_macos",
@@ -901,6 +898,7 @@
"-lrive_yoga_macos",
"-lrive_pls_renderer_macos",
"-lrive_cg_renderer_macos",
"-lrive_decoders_macos",
);
PRODUCT_BUNDLE_IDENTIFIER = rive.app.ios.runtime.RiveRuntime;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2023 Rive
*/
#import <PlatformCGImage.h>
#include "rive/core/type_conversions.hpp"
#include "utils/auto_cf.hpp"
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
#include <CoreGraphics/CoreGraphics.h>
#include <ImageIO/ImageIO.h>
#elif TARGET_OS_MAC
#include <ApplicationServices/ApplicationServices.h>
#endif
bool PlatformCGImageDecode(const uint8_t* encodedBytes,
size_t encodedSizeInBytes,
PlatformCGImage* platformImage)
{
AutoCF data = CFDataCreate(kCFAllocatorDefault, encodedBytes, encodedSizeInBytes);
if (!data)
{
return false;
}
AutoCF source = CGImageSourceCreateWithData(data, nullptr);
if (!source)
{
return false;
}
AutoCF image = CGImageSourceCreateImageAtIndex(source, 0, nullptr);
if (!image)
{
return false;
}
bool isOpaque = false;
switch (CGImageGetAlphaInfo(image.get()))
{
case kCGImageAlphaNone:
case kCGImageAlphaNoneSkipFirst:
case kCGImageAlphaNoneSkipLast:
isOpaque = true;
break;
default:
break;
}
const size_t width = CGImageGetWidth(image);
const size_t height = CGImageGetHeight(image);
const size_t rowBytes = width * 4; // 4 bytes per pixel
const size_t size = rowBytes * height;
const size_t bitsPerComponent = 8;
CGBitmapInfo cgInfo = kCGBitmapByteOrder32Big; // rgba
if (isOpaque)
{
cgInfo |= kCGImageAlphaNoneSkipLast;
}
else
{
cgInfo |= kCGImageAlphaPremultipliedLast;
}
std::vector<uint8_t> pixels;
pixels.resize(size);
AutoCF cs = CGColorSpaceCreateDeviceRGB();
AutoCF cg =
CGBitmapContextCreate(pixels.data(), width, height, bitsPerComponent, rowBytes, cs, cgInfo);
if (!cg)
{
return false;
}
CGContextSetBlendMode(cg, kCGBlendModeCopy);
CGContextDrawImage(cg, CGRectMake(0, 0, width, height), image);
platformImage->width = rive::castTo<uint32_t>(width);
platformImage->height = rive::castTo<uint32_t>(height);
platformImage->opaque = isOpaque;
platformImage->pixels = std::move(pixels);
return true;
}

View File

@@ -8,8 +8,6 @@
#import <RivePrivateHeaders.h>
#import <RiveFactory.h>
#import <PlatformCGImage.h>
#include "utils/auto_cf.hpp"
#include "cg_factory.hpp"
#include "cg_renderer.hpp"
@@ -54,44 +52,8 @@ static std::unique_ptr<rive::pls::PLSRenderContext> make_pls_context_native(id<M
NSLog(@"error: GPU is not Apple family");
return nullptr;
}
class PLSRenderContextNativeImpl : public rive::pls::PLSRenderContextMetalImpl
{
public:
PLSRenderContextNativeImpl(id<MTLDevice> gpu) :
PLSRenderContextMetalImpl(gpu, ContextOptions())
{}
protected:
rive::rcp<rive::pls::PLSTexture> decodeImageTexture(
rive::Span<const uint8_t> encodedBytes) override
{
PlatformCGImage image;
if (!PlatformCGImageDecode(encodedBytes.data(), encodedBytes.size(), &image))
{
return nullptr;
}
// CG only supports premultiplied alpha. Unmultiply now.
size_t imageSizeInBytes = image.height * image.width * 4;
for (size_t i = 0; i < imageSizeInBytes; i += 4)
{
auto alpha = image.pixels[i + 3];
if (alpha != 0.0f)
{
auto alphaFactor = 255.0f / alpha;
for (size_t j = 0; j < 3; j++)
{
image.pixels[i + j] *= alphaFactor;
}
}
}
uint32_t mipLevelCount = rive::math::msb(image.height | image.width);
return makeImageTexture(image.width, image.height, mipLevelCount, image.pixels.data());
}
};
auto plsContextImpl =
std::unique_ptr<PLSRenderContextNativeImpl>(new PLSRenderContextNativeImpl(gpu));
return std::make_unique<rive::pls::PLSRenderContext>(std::move(plsContextImpl));
return rive::pls::PLSRenderContextMetalImpl::MakeContext(
gpu, rive::pls::PLSRenderContextMetalImpl::ContextOptions());
}
- (instancetype)init

View File

@@ -1,23 +0,0 @@
/*
* Copyright 2023 Rive
*/
#pragma once
#include <vector>
// Represents raw, premultiplied, RGBA image data with tightly packed rows (width * 4 bytes).
struct PlatformCGImage
{
uint32_t width = 0;
uint32_t height = 0;
bool opaque = false;
std::vector<uint8_t> pixels;
};
// Decodes the given bytes into 'platformImage'.
//
// Returns false and leaves 'platformImage' unchaged on failure.
[[nodiscard]] bool PlatformCGImageDecode(const uint8_t* encodedBytes,
size_t encodedSizeInBytes,
PlatformCGImage* platformImage);

View File

@@ -57,13 +57,21 @@ build_runtime() {
# Build rive_pls_renderer.
pushd $RIVE_PLS_DIR
premake5 --config=$1 --out=out/iphoneos_$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --file=premake5_pls_renderer.lua --no-rive-decoders --os=ios gmake2
premake5 --config=$1 --out=out/iphoneos_$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --file=premake5_pls_renderer.lua --os=ios gmake2
make -C out/iphoneos_$1 clean
make -C out/iphoneos_$1 -j12 rive_pls_renderer
popd
cp -r $RIVE_PLS_DIR/out/iphoneos_$1/librive_pls_renderer.a $DEV_SCRIPT_DIR/../dependencies/$1/librive_pls_renderer.a
$DEV_SCRIPT_DIR/strip_static_lib.sh $DEV_SCRIPT_DIR/../dependencies/$1/librive_pls_renderer.a
cp -r $RIVE_PLS_DIR/include $DEV_SCRIPT_DIR/../dependencies/includes/pls
# Build rive_decoders.
pushd $RIVE_RUNTIME_DIR/decoders
premake5 --file=premake5_v2.lua --config=$1 --out=out/iphoneos_$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --os=ios gmake2
make -C out/iphoneos_$1 clean
make -C out/iphoneos_$1 -j12 rive_decoders
popd
cp -r $RIVE_RUNTIME_DIR/decoders/out/iphoneos_$1/librive_decoders.a $DEV_SCRIPT_DIR/../dependencies/$1/librive_decoders.a
}
build_runtime_sim() {
@@ -90,13 +98,21 @@ build_runtime_sim() {
# Build rive_pls_renderer.
pushd $RIVE_PLS_DIR
premake5 --config=$1 --out=out/iphonesimulator_$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --file=premake5_pls_renderer.lua --no-rive-decoders --os=ios --variant=emulator gmake2
premake5 --config=$1 --out=out/iphonesimulator_$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --file=premake5_pls_renderer.lua --os=ios --variant=emulator gmake2
make -C out/iphonesimulator_$1 clean
make -C out/iphonesimulator_$1 -j12 rive_pls_renderer
popd
cp -r $RIVE_PLS_DIR/out/iphonesimulator_$1/librive_pls_renderer.a $DEV_SCRIPT_DIR/../dependencies/$1/librive_pls_renderer_sim.a
$DEV_SCRIPT_DIR/strip_static_lib_fat.sh $DEV_SCRIPT_DIR/../dependencies/$1/librive_pls_renderer_sim.a arm64 x86_64
cp -r $RIVE_PLS_DIR/include $DEV_SCRIPT_DIR/../dependencies/includes/pls
# Build rive_decoders.
pushd $RIVE_RUNTIME_DIR/decoders
premake5 --file=premake5_v2.lua --config=$1 --out=out/iphonesimulator_$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --os=ios --variant=emulator gmake2
make -C out/iphonesimulator_$1 clean
make -C out/iphonesimulator_$1 -j12 rive_decoders
popd
cp -r $RIVE_RUNTIME_DIR/decoders/out/iphonesimulator_$1/librive_decoders.a $DEV_SCRIPT_DIR/../dependencies/$1/librive_decoders_sim.a
}
build_runtime_macosx() {
@@ -122,13 +138,21 @@ build_runtime_macosx() {
# Build rive_pls_renderer.
pushd $RIVE_PLS_DIR
premake5 --config=$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --file=premake5_pls_renderer.lua --no-rive-decoders --os=macosx gmake2
premake5 --config=$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --file=premake5_pls_renderer.lua --os=macosx gmake2
make -C out/$1 clean
make -C out/$1 -j12 rive_pls_renderer
popd
cp -r $RIVE_PLS_DIR/out/$1/librive_pls_renderer.a $DEV_SCRIPT_DIR/../dependencies/$1/librive_pls_renderer_macos.a
$DEV_SCRIPT_DIR/strip_static_lib_fat.sh $DEV_SCRIPT_DIR/../dependencies/$1/librive_pls_renderer_macos.a arm64 x86_64
cp -r $RIVE_PLS_DIR/include $DEV_SCRIPT_DIR/../dependencies/includes/pls
# Build rive_decoders.
pushd $RIVE_RUNTIME_DIR/decoders
premake5 --file=premake5_v2.lua --config=$1 --out=out/$1 --arch=universal --scripts=$RIVE_RUNTIME_DIR/build --os=macosx gmake2
make -C out/$1 clean
make -C out/$1 -j12 rive_decoders
popd
cp -r $RIVE_RUNTIME_DIR/decoders/out/$1/librive_decoders.a $DEV_SCRIPT_DIR/../dependencies/$1/librive_decoders_macos.a
}
usage() {