nativeshadr is an experimental R package for developers to assist writing pixel shaders as Rcpp codes. It brings HLSL-like syntax via the HLSL++ library so that users can write pseudo HLSL shaders in Rcpp.
Typical usage of nativeshadr is as follows:
shader(int2 wh, RcppParallel::RMatrix<int> nr, const vvd& uniforms),
where wh is the position of a pixel in the texture, nr is a
‘nativeRaster’ object that represents the texture, and uniforms is
a std::vector<std::vector<double>> object that represents a list
of uniforms.uint32_t value that represents
the color of the pixel as the same manner in ‘nativeRaster’ objects.vectorize_shader() is provided, which takes a
pixel shader as its argument and returns a vectorized function.In order to enable completion in your IDE, it is highly recommended to
write shaders as R packages. However, you can also write them as scripts
and compile them with Rcpp::sourceCpp().
library(nara) ## https://github.com/coolbutuseless/nara
Rcpp::sourceCpp(code = R"{
// [[Rcpp::depends(RcppParallel, nativeshadr)]]
#include <nativeshadr.h>
uint32_t gradient(int2 wh, RMatrix<int> nr, const vvd& uniforms) {
float2 uv = float2(wh) / float2(nr.ncol(), nr.nrow());
float4 col = float4(uv.x, uv.y, .6, 1);
return int4_to_icol(clamp(col * 255, 0, 255));
}
// [[Rcpp::export]]
Rcpp::IntegerVector test_gradient(Rcpp::IntegerMatrix nr) {
const vvd uniforms;
return vectorize_shader(gradient)(nr, uniforms);
}
}")
test_gradient(nara::nr_new(640, 360)) |>
plot()

Rcpp::sourceCpp(code = R"{
// [[Rcpp::depends(RcppParallel, nativeshadr)]]
#include <nativeshadr.h>
uint32_t sepia(int2 wh, RMatrix<int> nr, const vvd& uniforms) {
const std::vector<double>& intensity = uniforms[0];
const std::vector<double>& depth = uniforms[1];
float4 texture = texture_eval(nr, wh);
float3 rgb = clamp(float3(
texture.x + depth[0] * 2.0,
texture.y + depth[0],
(texture.x + texture.y + texture.z) / 3.0
), 0, 255);
rgb.z = clamp(rgb.z - rgb.z * intensity[0], 0, 255);
return int4_to_icol(clamp(float4(rgb, texture.w), 0, 255));
}
// [[Rcpp::export]]
Rcpp::IntegerVector test_sepia(Rcpp::IntegerMatrix nr, Rcpp::List uni) {
const vvd uniforms = {uni["intensity"], uni["depth"]};
return vectorize_shader(sepia)(nr, uniforms);
}
}")
fastpng::read_png(system.file("images/river.png", package = "nativeshadr"), type = "nativeraster") |>
test_sepia(list(intensity = .6, depth = 60.0)) |>
plot()

Rcpp::sourceCpp(code = R"{
// [[Rcpp::depends(RcppParallel, nativeshadr)]]
#include <nativeshadr.h>
uint32_t ring(int2 wh, RMatrix<int> nr, const vvd& uniforms) {
const std::vector<double>& mouse = uniforms[0];
const std::vector<double>& time = uniforms[1];
float2 resolution = float2(nr.ncol(), nr.nrow());
float2 m = float2(mouse[0] * 2.0 - 1.0, -1.0 * mouse[1] * 2.0 + 1.0);
float2 p = (float2(wh) * 2.0 - resolution) / min(resolution.x, resolution.y);
float u = sin((hlslpp::atan2(p.y, p.x) + time[0] * .5) * 20.0) * .01;
float t = 0.02 / abs((sin(time[0]) + 1.0) * 0.5 + u - length(p));
return int4_to_icol(clamp(float4(t, t, t, 1.0) * 255, 0, 255));
}
// [[Rcpp::export]]
Rcpp::IntegerVector test_ring(Rcpp::IntegerMatrix nr, Rcpp::List uni) {
const vvd uniforms = {uni["mouse"], uni["time"]};
return vectorize_shader(ring)(nr, uniforms);
}
}")
timing <- Sys.time()
fig_path <-
gifski::save_gif(
{
for (frame in seq(0, 4 * pi, length.out = 60)) {
img <- test_ring(nara::nr_new(640, 360), list(mouse = c(0.5, 0.5), time = frame * 1000))
grid::grid.newpage()
grid::grid.raster(img, interpolate = FALSE)
}
},
width = 320,
height = 180,
gif_file = "man/figures/README-example-animated-ring.gif",
delay = 1 / 12
)
Sys.time() - timing
#> Time difference of 0.7881124 secs

MIT license.