private static string GetSource(IShaderBuilder builder) { return($@" {builder.SrvType} in_tex : register(t0); cbuffer InfoBuffer : register(b0) {{ uint layer; // contains offset z for 3d textures uint level; uint xoffset; uint yoffset; float multiplier; }}; struct PixelIn {{ float2 texcoord : TEXCOORD; float4 projPos : SV_POSITION; uint depth : SV_RenderTargetArrayIndex; }}; float4 main(PixelIn i) : SV_TARGET {{ float4 coord = i.projPos; float4 color = in_tex.mips[level][uint3(xoffset + uint(coord.x), yoffset + uint(coord.y), layer + i.depth)]; color.rgb *= multiplier; return color; }} "); }
// source if the dst dimension is a multiple of the src dimension private string GetFastSource(IShaderBuilder builder) { return($@" {AdditionalBindings} {WeightFunc(weightFunc)} {HeaderAndMain(builder)} {{ {ReturnIfOutside()} int fullFilterSize = numSrcPixels * {kernelStretch}; int3 srcPos = id * dir * numSrcPixels + id * (1 - dir); #if {kernelStretch} != 1 int stretchOffset = uint(numSrcPixels * ({kernelStretch} - 1)) / 2; srcPos = srcPos - dir * stretchOffset; #endif float factorScale = 2.0 / fullFilterSize; float factorOffset = fullFilterSize / 2.0; // sum up pixels from the src image {builder.Double}4 dstColor = 0.0; float leftWeight = weight(-1.0); for(int i = 1; i <= fullFilterSize; ++i){{ // interpolation value [-1, 1] (i - size / 2) / (size / 2) float rightWeight = weight((float(i) - factorOffset) * factorScale); {AddWeightedPixel(builder)} }} {NormalizeAndWriteBackColor()} }} "); }
private static string GetSlowSource(IShaderBuilder builder, bool veryDetailed) { return($@" {HeaderAndMain(builder, veryDetailed)} // {{ float3 startPosf = id * filterSizeFloat; float3 endPosf = (id + 1) * filterSizeFloat; uint3 startPos = floor(startPosf); uint3 endPos = min(ceil(endPosf), srcSize); uint3 coord = 0; for(coord.z = startPos.z; coord.z < endPos.z; ++coord.z) for(coord.y = startPos.y; coord.y < endPos.y; ++coord.y) {{ // coverage of the current pixel const float yzVis = getVisibility(coord.z, startPosf.z, endPosf.z) * getVisibility(coord.y, startPosf.y, endPosf.y); for(coord.x = startPos.x; coord.x < endPos.x; ++coord.x) {{ float4 c = src_image[texel(coord)]; // multiply weight by pixel coverage float w = weight(toSrgb(c)) * yzVis * getVisibility(coord.x, startPosf.x, endPosf.x); weightSum += w; dcolor.a += double(c.a * w); dcolor.rgb += double3(c.a * c.rgb * w); }} }} {NormalizeAndWriteBackColor()} }}"); }
private string GetSource(IShaderBuilder builderIn, IShaderBuilder builderOut) { return($@" {GetInputs(builderIn)} {builderOut.UavType} out_image : register(u0); cbuffer InputBuffer : register(b0) {{ int layer; int3 size; }}; {Utility.Utility.ToSrgbFunction()} {builderIn.TexelHelperFunctions} {builderOut.Type} transform({builderIn.IntVec} coord) {{ {transform}; }} [numthreads({builderIn.LocalSizeX}, {builderIn.LocalSizeY}, {builderIn.LocalSizeZ})] void main(int3 id : SV_DispatchThreadID) {{ if(any(id >= size)) return; out_image[texel(id, layer)] = transform(texel(id)); }} "); }
private static string HeaderAndMain(IShaderBuilder builder) { return($@" {builder.SrvSingleType} src_image : register(t0); {builder.UavType} dst_image : register(u0); cbuffer InputBuffer : register(b0) {{ int3 dir; // direction of the filter int numSrcPixels; // number of pixels to process (per Kernel) uint3 dstSize; // size of the destination image bool hasAlpha; // indicates if any pixel has alpha != 0 int numSrcPixelsTotal; // total number of src pixels for the current direction float filterSize; // (float) number of pixels to process (per Kernel) uint layer; }}; int3 clampCoord(int3 coord) {{ return clamp(dot(coord, dir), 0, numSrcPixelsTotal - 1) * dir + (1-dir) * coord; }} {builder.TexelHelperFunctions} [numthreads({builder.LocalSizeX}, {builder.LocalSizeY}, {builder.LocalSizeZ})] void main(uint3 id : SV_DispatchThreadID)"); }
private static string GetSource(IShaderBuilder builder) { return($@" {builder.SrvSingleType} src_image : register(t0); cbuffer InputBuffer : register(b0) {{ int3 pixelCoord; int radius; int3 size; }}; RWStructuredBuffer<float4> out_buffer : register(u0); [numthreads(1, 1, 1)] void main(){{ float4 sum = float4(0.0, 0.0, 0.0, 0.0); {(builder.Is3D?"for(int z = pixelCoord.z - radius; z <= pixelCoord.z + radius; ++z)":"int z = 0;")} for(int x = pixelCoord.x - radius; x <= pixelCoord.x + radius; ++x) for(int y = pixelCoord.y - radius; y <= pixelCoord.y + radius; ++y) {{ int3 pixel = clamp(int3(x, y, z), int3(0, 0, 0), size - int3(1, 1, 1)); {(builder.Is3D? "sum += src_image[pixel];" : "sum += src_image[pixel.xy];")} }} uint width = 1 + 2 * radius; float areaInv = 1.0f / float(width * width {(builder.Is3D?"* width":"")}); out_buffer[0] = sum * areaInv; }} "); }
private static string HeaderAndMain(IShaderBuilder builder, bool veryDetailed) { return($@" {builder.SrvSingleType} src_image : register(t0); {builder.SrvSingleType} guide : register(t1); {builder.UavType} dst_image : register(u0); cbuffer InputBuffer : register(b0) {{ uint3 srcSize; uint layer; uint3 dstSize; bool hasAlpha; float3 filterSizeFloat; }}; {Utility.Utility.ToSrgbFunctionUncapped()} static float4 guideValue; // in sRGB space (perceived difference) // color: color in sRGB space float weight(float4 color) {{ // according to paper: // (length(guide - color) / Vmax) ^ y with y == 0.5, or y == 1.0 for very detailed // Vmax = maximal color difference // sRGB difference float3 diff = (color.rgb - guideValue.rgb); // TODO multiply with alpha? diff *= diff; // give luma weights to each color => assuming max difference of (1, 1, 1) leads to weightedDiff [0, 1] => Vmax = 1 float weightedDiff = dot(diff, float3(0.299, 0.587, 0.114)); // weightedDiff ~ length(diff) ^ 2 return {(veryDetailed ? "sqrt(weightedDiff)" : "pow(weightedDiff, 0.25)")}; }} // indicates how much of the box is covered float getVisibility(float position, float start, float end) {{ if(position < start) return position + 1.0 - start; return min(end - position, 1.0); }} {builder.TexelHelperFunctions} float weight(); [numthreads({builder.LocalSizeX/2}, {builder.LocalSizeY/2}, {Math.Max(builder.LocalSizeZ/2, 1)})] void main(uint3 id : SV_DispatchThreadID){{ if(any(id >= dstSize)) return; guideValue = toSrgb(guide[texel(id)]); double4 dcolor = 0.0; double weightSum = 0.0; "); }
// just copies pixels because the dimension match (numSrcPixel == dir*dstSize) public static string GetCopySource(IShaderBuilder builder) { return($@" {HeaderAndMain(builder)} {{ {ReturnIfOutside()} dst_image[texel(id, layer)] = src_image[texel(id)]; }} "); }
private string GetSource(IShaderBuilder builder) { return($@" {builder.SrvType} src_image : register(t0); RWStructuredBuffer<float> dst_buffer : register(u0); cbuffer InputBuffer : register(b0) {{ uint level; bool trueBool; }}; {Utility.Utility.ToSrgbFunction()} struct ComputeIn {{ uint3 global : SV_DispatchThreadID; }}; bool isNan(float v) {{ float vi = v; if(!trueBool) vi = 0.0; // true bool is always true, so this won't be executed // little trick to detect nans because v[i] != v[i] will be optimized away. return v != vi; }} float4 zeroNans(float4 v) {{ float4 res = v; [unroll] for(uint i = 0; i < 4; ++i) {{ if(isNan(v[i])) res[i] = 0.0; }} return res; }} float getPixel(int3 pos) {{ float4 value = src_image.mips[level][pos]; value = zeroNans(value); {returnValue}; }} [numthreads({LocalSizeX}, {LocalSizeY}, 1)] void main(ComputeIn i){{ uint3 pos = i.global.xyz; uint width, height, depth, numLevels; src_image.GetDimensions(level, width, height, depth, numLevels); if(pos.x >= width || pos.y >= height) return; float value = getPixel(pos); dst_buffer[pos.z * width * height + pos.y * width + pos.x] = value; }} "); }
private string GetShaderHeader(FilterLoader.Type kernel, IShaderBuilder builder) { string filterDirectionVar = parent.IsSepa ? "int3 filterDirection;" : ""; return($@" {builder.UavType} dst_image : register(u0); {builder.SrvSingleType} src_image : register(t0); {builder.SrvType} src_image_ex : register(t1); SamplerState linearSampler : register(s0); SamplerState pointSampler : register(s1); cbuffer LayerLevelBuffer : register(b0) {{ uint layer; uint level; uint2 LayerLevelBuffer_padding_; {filterDirectionVar} }}; // texel helper function #if {builder.Is3DInt} int3 texel(int3 coord) {{ return coord; }} int3 texel(int3 coord, int layer) {{ return coord; }} #else int2 texel(int3 coord) {{ return coord.xy; }} int3 texel(int3 coord, int layer) {{ return int3(coord.xy, layer); }} #endif float4 filter{GetKernelDeclaration(kernel)}; [numthreads({localSize}, {localSize}, {(builder.Is3D?localSize:1)})] void main(uint3 coord : SV_DISPATCHTHREADID) {{ uint width, height, depth; dst_image.GetDimensions(width, height, depth); uint3 dstCoord = coord; uint3 size = int3(width, height, depth); if(coord.x >= width || coord.y >= height) return; #if {builder.Is3DInt} if(coord.z >= depth) return; #else dstCoord.z = layer; size.z = 1; #endif #if {(kernel == FilterLoader.Type.Tex2D?1:0)} dst_image[dstCoord] = filter(coord.xy, size.xy); #elif {(kernel == FilterLoader.Type.Color ? 1 : 0)} dst_image[dstCoord] = filter(src_image[texel(coord)]); #else // dynamic or 3D dst_image[dstCoord] = filter(coord, size); #endif }} " + GetParamBufferDescription(parent.Parameters) + GetTextureParamBindings(parent.TextureParameters, builder)); }
private string GetInputs(IShaderBuilder builder) { string res = ""; int id = 0; foreach (var input in inputs) { res += $"{builder.SrvSingleType} {input} : register(t{id++});\n"; } return(res); }
private static string GetTextureGetters(int numImages, IShaderBuilder builder) { var res = ""; for (int i = 0; i < numImages; ++i) { res += $"float4 GetTexture{i}(uint3 pixel)" + "{\n"; res += $"return texture{i}.mips[level][uint3(pixel.x, pixel.y, pixel.z)];" + "\n}\n"; } return(res); }
private static string GetTextureBindings(int numImages, IShaderBuilder builder) { var res = ""; for (int i = 0; i < numImages; ++i) { // binding res += $"{builder.SrvType} texture{i} : register(t{i});\n"; } return(res); }
public static string GetPixelSource(IShaderBuilder builder) { return($@" {builder.SrvSingleType} tex : register(t0); SamplerState texSampler : register(s0); cbuffer InfoBuffer : register(b0) {{ matrix transform; float4 crop; float multiplier; float farplane; bool useAbs; int xaxis; int yaxis; int zvalue; }}; {Utility.ToSrgbFunction()} struct PixelIn {{ float4 projPos : SV_POSITION; float2 texcoord : TEXCOORD; }}; #if {builder.Is3DInt} float3 getCoord(float2 coord){{ float res[3]; uint size[3]; tex.GetDimensions(size[0], size[1], size[2]); res[xaxis] = coord.x; res[yaxis] = coord.y; // convert zvalue to float res[3 - xaxis - yaxis] = (zvalue + 0.5f) / size[3 - xaxis - yaxis]; return float3(res[0], res[1], res[2]); }} #else float2 getCoord(float2 coord) {{ return coord; }} #endif float4 main(PixelIn i) : SV_TARGET {{ float4 color = tex.Sample(texSampler, getCoord(i.texcoord)); color.rgb *= multiplier; #if {1-builder.Is3DInt} {ApplyColorCrop("i.texcoord")} #endif {ApplyColorTransform()} return color; }} "); }
private static string GetSource(IShaderBuilder builder) { return($@" {builder.SrvSingleType} in_tex : register(t0); SamplerState texSampler : register(s0); cbuffer InfoBuffer : register(b0) {{ float3 offset; uint depth; float3 scale; }} struct PixelIn {{ float2 texcoord : TEXCOORD; float4 projPos : SV_POSITION; #if {builder.Is3DInt} uint depth : SV_RenderTargetArrayIndex; #endif }}; #if {builder.Is3DInt} #define TexcoordT float3 float3 toTex(float3 t) {{ return t; }} #else #define TexcoordT float2 float2 toTex(float3 t) {{ return t.xy; }} #endif float4 main(PixelIn i) : SV_TARGET {{ TexcoordT coord; // dst texture coordinate coord.xy = i.texcoord; #if {builder.Is3DInt} coord.z = (i.depth + 0.5f) / depth; #endif // calc src texture coordinate coord = (coord - toTex(offset)) * toTex(scale); return in_tex.Sample(texSampler, coord); }} "); }
private string GetSource(IShaderBuilder builder) { return($@" {builder.SrvSingleType} src_image : register(t0); {builder.UavType} out_image : register(u0); cbuffer InputBuffer : register(b0) {{ int layer; int3 dir; // filter direction int3 size; }}; {builder.TexelHelperFunctions} float kernel(int offset) {{ return exp(-0.5 * (offset * offset) / {variance}); }} bool isInside(int3 pos) {{ return all(pos < size) && all(pos >= 0); }} [numthreads({builder.LocalSizeX}, {builder.LocalSizeY}, {builder.LocalSizeZ})] void main(int3 id : SV_DispatchThreadID) {{ if(any(id >= size)) return; float weightSum = 0.0; {builder.Type} pixelSum = 0.0; int3 pos = id - {radius} * dir; [unroll] for(int i = -{radius}; i <= {radius}; ++i) {{ [flatten] if(isInside(pos)){{ float w = kernel(i); weightSum += w; pixelSum += w * src_image[texel(pos)]; }} pos += dir; }} out_image[texel(id, layer)] = pixelSum / weightSum; }} "); }
private string AddWeightedPixel(IShaderBuilder builder) { return($@" float w = rightWeight - leftWeight; #if {kernelStretch} != 1 float4 v = src_image[texel(clampCoord(srcPos))]; #else float4 v = src_image[texel(srcPos)]; #endif dstColor.a += {builder.Double}(v.a * w); dstColor.rgb += {builder.Double}3(v.a * v.rgb * w); srcPos += dir; leftWeight = rightWeight; "); }
// source if the dst dimension is not a multiple of the src dimension (special border handling) public string GetSlowSource(IShaderBuilder builder) { return($@" {AdditionalBindings} {WeightFunc(weightFunc)} {HeaderAndMain(builder)} {{ {ReturnIfOutside()} // interval in float coordinates float startf = dot(id, dir) * filterSize; float endf = (dot(id, dir) + 1) * filterSize; #if {kernelStretch} != 1 float stretchOffset = (filterSize * ({kernelStretch} - 1)) / 2.0; startf -= stretchOffset; endf += stretchOffset; #endif uint starti = floor(startf); #if {kernelStretch} == 1 int endi = min(ceil(endf), numSrcPixelsTotal); #else int endi = ceil(endf); #endif int3 srcPos = dir * starti + (1 - dir) * id; float factorScale = 2.0 / (filterSize * {kernelStretch}); float factorOffset = -startf - (filterSize * {kernelStretch}) / 2.0; // sum up pixels {builder.Double}4 dstColor = 0.0; float leftWeight = weight(-1.0); for(int i = starti + 1; i <= endi; ++i){{ // from [startf, endf] => [-1, 1] float rightWeight = weight(min((float(i) + factorOffset) * factorScale, 1.0)); {AddWeightedPixel(builder)} }} {NormalizeAndWriteBackColor()} }} "); }
private static string GetSource(IShaderBuilder builder) { return($@" {builder.SrvType} in_tex : register(t0); {builder.SrvType} in_overlay : register(t1); cbuffer InfoBuffer : register(b0) {{ uint layer; // contains offset z for 3d textures uint level; uint xoffset; uint yoffset; float multiplier; bool useOverlay; uint scale; }}; struct PixelIn {{ float2 texcoord : TEXCOORD; float4 projPos : SV_POSITION; #if {builder.Is3DInt} uint depth : SV_RenderTargetArrayIndex; #endif }}; float4 main(PixelIn i) : SV_TARGET {{ uint3 coord = uint3((uint2(i.projPos.xy) + uint2(xoffset, yoffset)) / scale, layer); #if {builder.Is3DInt} coord.z = (i.depth + layer) / scale; #endif float4 color = in_tex.mips[level][coord]; color.rgb *= multiplier; if(useOverlay) {{ // overlay has alpha premultiplied color and alpha channel contains inverse alpha (occlusion) float4 overlay = in_overlay.mips[level][coord]; color.rgb = overlay.rgb + overlay.a * color.rgb; color.a = 1.0 - (overlay.a * (1.0 - color.a)); }} return color; }} "); }
private static string GetSource(IShaderBuilder builder) { return($@" {builder.SrvSingleType} src_image : register(t0); {builder.UavType} dst_image : register(u0); cbuffer InputBuffer : register(b0) {{ int3 size; uint layer; bool hasAlpha; }}; {builder.TexelHelperFunctions} [numthreads({builder.LocalSizeX}, {builder.LocalSizeY}, {builder.LocalSizeZ})] void main(int3 id : SV_DispatchThreadID) {{ if(any(id >= size)) return; int3 coord = 0; float4 dstColor = 0.0; int weightSum = 0.0; // apply filter for(coord.z = max(id.z - 1, 0); coord.z <= min(id.z + 1, size.z - 1); ++coord.z) for(coord.y = max(id.y - 1, 0); coord.y <= min(id.y + 1, size.y - 1); ++coord.y) for(coord.x = max(id.x - 1, 0); coord.x <= min(id.x + 1, size.x - 1); ++coord.x) {{ float4 v = src_image[texel(coord)]; int iw = 4 >> dot(abs(id - coord), 1); // 4 >> 0 for center, 4 >> 1 == 2 for one difference, 4 >> 2 == 1 for two difference weightSum += iw; float w = float(iw); dstColor.a += v.a * w; dstColor.rgb += v.a * v.rgb * w; }} dstColor /= float(weightSum); if(!hasAlpha) dstColor.a = 1.0; if(dstColor.a != 0.0) dstColor.rgb /= dstColor.a; dst_image[texel(id, layer)] = dstColor; }} "); }
private static string GetSource(IShaderBuilder builder) { return($@" {builder.SrvSingleType} tex : register(t0); SamplerState texSampler : register(s0); struct PixelIn {{ float2 texcoord : TEXCOORD; float4 projPos : SV_POSITION; }}; float4 main(PixelIn i) : SV_TARGET {{ float4 color = tex.Sample(texSampler, {(builder.Is3D?"float3(i.texcoord, 0.49)": "i.texcoord")}); return color; }} "); }
private static string GetFastSource(IShaderBuilder builder, bool veryDetailed) { return($@" {HeaderAndMain(builder, veryDetailed)} // {{ uint3 startPos = id * (srcSize / dstSize); uint3 endPos = (id + 1) * (srcSize / dstSize); uint3 coord = 0; for(coord.z = startPos.z; coord.z < endPos.z; ++coord.z) for(coord.y = startPos.y; coord.y < endPos.y; ++coord.y) for(coord.x = startPos.x; coord.x < endPos.x; ++coord.x) {{ float4 c = src_image[texel(coord)]; float w = weight(toSrgb(c)); weightSum += w; dcolor.a += double(c.a * w); dcolor.rgb += double3(c.a * c.rgb * w); }} {NormalizeAndWriteBackColor()} }}"); }
private string GetSourceWeighted(IShaderBuilder builder) { return($@" {builder.UavType} primary_layer : register(u0); {builder.SrvSingleType} other_layer[4] : register(t0); SamplerState texSampler : register(s0); cbuffer InputBuffer : register(b0) {{ int layer; int3 size; float invWeightSum; // 1.0 if all mipmaps are used int numMipmaps; }}; {builder.TexelHelperFunctions} [numthreads({builder.LocalSizeX}, {builder.LocalSizeY}, {builder.LocalSizeZ})] void main(int3 id : SV_DispatchThreadID) {{ if(any(id >= size)) return; // keep sign of upper layer (important for structure) float sign = primary_layer[texel(id, layer)] < 0 ? -1.0f : 1.0f; float color = pow(abs(primary_layer[texel(id, layer)]), 0.0448 * invWeightSum); float3 tId = (id + 0.5) / size; color *= pow(abs(other_layer[0].SampleLevel(texSampler, texel(tId), 0)), 0.2856 * invWeightSum); if(numMipmaps >= 3) color *= pow(abs(other_layer[1].SampleLevel(texSampler, texel(tId), 0)), 0.3001 * invWeightSum); if(numMipmaps >= 4) color *= pow(abs(other_layer[2].SampleLevel(texSampler, texel(tId), 0)), 0.2363 * invWeightSum); if(numMipmaps >= 5) color *= pow(abs(other_layer[3].SampleLevel(texSampler, texel(tId), 0)), 0.1333 * invWeightSum); primary_layer[texel(id, layer)] = sign * color; }} "); }
private string GetSourceCopy(IShaderBuilder builder) { return($@" {builder.UavType} dst_layer : register(u0); {builder.SrvSingleType} src_layer : register(t0); SamplerState texSampler : register(s0); cbuffer InputBuffer : register(b0) {{ int layer; int3 size; }}; {builder.TexelHelperFunctions} [numthreads({builder.LocalSizeX}, {builder.LocalSizeY}, {builder.LocalSizeZ})] void main(int3 id : SV_DispatchThreadID) {{ if(any(id >= size)) return; float3 tId = (id + 0.5) / size; dst_layer[texel(id, layer)] = src_layer.SampleLevel(texSampler, texel(tId), 0); }} "); }
private static string GetShaderSource(string colorFormula, string alphaFormula, int numImages, IShaderBuilder builder) { return // global variables ($@"cbuffer InfoBuffer {{ uint layer; uint level; }}; {GetTextureBindings(numImages, builder)} {GetTextureGetters(numImages, builder)} {GetHelperFunctions()} {builder.UavType} out_tex : register(u0); [numthreads({builder.LocalSizeX}, {builder.LocalSizeY}, {builder.LocalSizeZ})] void main(uint3 coord : SV_DISPATCHTHREADID) {{ uint width, height, depth, numLvl; texture0.GetDimensions(level, width, height, depth, numLvl); if(!{builder.Is3DString}) coord.z = layer; if(coord.x >= width || coord.y >= height) return; if({builder.Is3DString} && coord.z >= depth) return; float4 color = {GetImageColor(colorFormula, alphaFormula)}; out_tex[uint3(coord.x, coord.y, coord.z)] = color; }} "); }
public ImageCombineShader(string colorFormula, string alphaFormula, int numImages, IShaderBuilder builder) { this.builder = builder; shader = new DirectX.Shader(DirectX.Shader.Type.Compute, GetShaderSource(colorFormula, alphaFormula, Math.Max(numImages, 1), builder), "ImageCombineShader"); }
public FilterShader(FilterModel parent, string source, int groupSize, FilterLoader.Type kernel, IShaderBuilder builder) { localSize = groupSize; this.parent = parent; is3D = builder.Is3D; shader = new DirectX.Shader(DirectX.Shader.Type.Compute, GetShaderHeader(kernel, builder) + "\n#line 1\n" + source, parent.Filename); if (parent.Parameters.Count != 0) { paramBuffer = new UploadBuffer(4 * parent.Parameters.Count); UpdateParamBuffer(); } }
private static string GetTextureParamBindings(IReadOnlyList <TextureFilterParameterModel> parameters, IShaderBuilder builder) { if (parameters.Count == 0) { return(""); } string res = ""; var i = TextureBindingStart; foreach (var tex in parameters) { res += $"{builder.SrvSingleType} {tex.TextureName} : register(t{i++});\n"; } return(res); }
public SingleViewShader(IShaderBuilder builder) : base(GetVertexSource(), GetPixelSource(builder), "Single") { }
public PlainTextureView(ModelsEx models, TextureViewData data, IShaderBuilder builder) { this.models = models; this.data = data; shader = new SingleViewShader(builder); }