Shadertoy shader port to Metal slow on iOS - ios

I'm trying to learn Metal shaders so I ported this mountain generation shader I found on Shadertoy to Metal.
https://www.shadertoy.com/view/llsGW7
The port works, but is very slow on iOS. It is reasonably fast on OS X but gets slower when I increase the window size. It is also slow within an OS X playground.
I've done the tutorials on MetalKit.org and read the apple docs on the metal shading language but feel like I'm a bit conceptually lacking of how everything is working under the hood. If there is anything that jumps out in this code that is obviously slowing things down I would be very grateful to learn. I am not sure if the slowdown is due to the shader code itself, or the way it's all set up.
Here's the metal shader:
#include <metal_stdlib>
using namespace metal;
constexpr sampler textureSampler(coord::normalized,
address::repeat,
min_filter::linear,
mag_filter::linear,
mip_filter::linear );
kernel void compute(texture2d<float, access::write> output [[texture(0)]],
texture2d<float, access::sample> input [[texture(1)]],
constant float &timer [[buffer(0)]],
uint2 gid [[thread_position_in_grid]])
{
int width = input.get_width();
int height = input.get_height();
float2 uv = float2(gid) / float2(width, height);
float4 p = float4(uv,1,1)-0.5;
p.y = -p.y;
float4 d = p*0.5;
float4 t;
float4 c;
p.z += timer*200;
d.y-=0.2;
for(float i=1.7;i>0.0;i-=0.002) {
float s=0.5;
t = input.sample(textureSampler,0.3+p.xz*s/3e3) / (s+=s);
// this makes it purple
c = float4(1.0,-0.9,0.8,9.0)+d.x-t*i;
// c = float4(1.0,0.9,0.8,9.0)+d.x-t*i;
if (t.x > p.y*.01+1.3) {
break;
}
p += d;
}
output.write(c, gid);
}
and here is a subclass of MTKView I'm using to render the shader
import Cocoa
import MetalKit
class MetalView: MTKView {
var queue: MTLCommandQueue!
var cps: MTLComputePipelineState!
var timer: Float = 0
var timerBuffer: MTLBuffer!
var shaderName: String!
var texture: MTLTexture!
required public init(coder: NSCoder) {
super.init(coder: coder)
self.framebufferOnly = false
self.preferredFramesPerSecond = 60
registerShaders()
setupTexture()
}
func setupTexture() {
let path = Bundle.main.path(forResource: "texture", ofType: "jpg")
let textureLoader = MTKTextureLoader(device: device!)
texture = try! textureLoader.newTexture(withContentsOf: URL(fileURLWithPath: path!), options: nil)
}
func registerShaders() {
device = MTLCreateSystemDefaultDevice()!
queue = device!.makeCommandQueue()
do {
let library = device!.newDefaultLibrary()!
let kernel = library.makeFunction(name: "compute")!
cps = try device!.makeComputePipelineState(function: kernel)
} catch let e {
Swift.print("\(e)")
}
timerBuffer = device!.makeBuffer(length: MemoryLayout<Float>.size, options: [])
}
override public func draw(_ dirtyRect: CGRect) {
if let drawable = currentDrawable {
let commandBuffer = queue.makeCommandBuffer()
let commandEncoder = commandBuffer.makeComputeCommandEncoder()
commandEncoder.setComputePipelineState(cps)
commandEncoder.setTexture(drawable.texture, at: 0)
commandEncoder.setTexture(texture, at: 1)
commandEncoder.setBuffer(timerBuffer, offset: 0, at: 0)
update()
let threadGroupCount = MTLSizeMake(8, 8, 1)
let threadGroups = MTLSizeMake(drawable.texture.width / threadGroupCount.width, drawable.texture.height / threadGroupCount.height, 1)
commandEncoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupCount)
commandEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
func update() {
timer += Float(1.0 / TimeInterval(self.preferredFramesPerSecond))
let bufferPointer = timerBuffer.contents()
memcpy(bufferPointer, &timer, MemoryLayout<Float>.size)
}
}

Related

Rotating Metal texture 180 degrees

I added a sample code if someone want's to try fixing it: https://www.dropbox.com/s/6t6neo40qjganra/To%20be%20fixed.zip?dl=1
Im making an AR app using Vuforia SDK. Their sample code contains video background rendering using metal. It works fine for vertical orientation, but I need to change it to portrait. The problem is, that after changing it, the video is rendered upside down. Detected targets are in correct orientation, so I think this should be fixed in Metal rendering class. Could someone help me do that? Below is the code Im using to draw that background. How can I rotate it 180 degrees?
private var mMetalDevice: MTLDevice
private var mVideoBackgroundPipelineState: MTLRenderPipelineState!
private var mUniformColorShaderPipelineState: MTLRenderPipelineState!
private var mTexturedVertexShaderPipelineState: MTLRenderPipelineState!
private var mDefaultSamplerState: MTLSamplerState?
private var mVideoBackgroundVertices: MTLBuffer!
private var mVideoBackgroundIndices: MTLBuffer!
private var mVideoBackgroundTextureCoordinates: MTLBuffer!
/// Initialize the renderer ready for use
init(metalDevice: MTLDevice, layer: CAMetalLayer, library: MTLLibrary?, textureDepth: MTLTexture) {
mMetalDevice = metalDevice
let stateDescriptor = MTLRenderPipelineDescriptor()
//
// Video background
//
stateDescriptor.vertexFunction = library?.makeFunction(name: "texturedVertex")
stateDescriptor.fragmentFunction = library?.makeFunction(name: "texturedFragment")
stateDescriptor.colorAttachments[0].pixelFormat = layer.pixelFormat
stateDescriptor.depthAttachmentPixelFormat = textureDepth.pixelFormat
// And create the pipeline state with the descriptor
do {
try mVideoBackgroundPipelineState = metalDevice.makeRenderPipelineState(descriptor: stateDescriptor)
} catch {
print("Failed to create video background render pipeline state:",error)
}
//
// Augmentations
//
// Create pipeline for transparent object overlays
stateDescriptor.vertexFunction = library?.makeFunction(name: "uniformColorVertex")
stateDescriptor.fragmentFunction = library?.makeFunction(name: "uniformColorFragment")
stateDescriptor.colorAttachments[0].pixelFormat = layer.pixelFormat
stateDescriptor.colorAttachments[0].isBlendingEnabled = true
stateDescriptor.colorAttachments[0].rgbBlendOperation = .add
stateDescriptor.colorAttachments[0].alphaBlendOperation = .add
stateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
stateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
stateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
stateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
stateDescriptor.depthAttachmentPixelFormat = textureDepth.pixelFormat
do {
try mUniformColorShaderPipelineState = metalDevice.makeRenderPipelineState(descriptor: stateDescriptor)
} catch {
print("Failed to create augmentation render pipeline state:",error)
return
}
stateDescriptor.vertexFunction = library?.makeFunction(name: "texturedVertex")
stateDescriptor.fragmentFunction = library?.makeFunction(name: "texturedFragment")
// Create pipeline for rendering textures
do {
try mTexturedVertexShaderPipelineState = metalDevice.makeRenderPipelineState(descriptor: stateDescriptor)
} catch {
print("Failed to create guide view render pipeline state:", error)
return
}
mDefaultSamplerState = MetalRenderer.defaultSampler(device: metalDevice)
// Allocate space for rendering data for Video background
mVideoBackgroundVertices = mMetalDevice.makeBuffer(length: MemoryLayout<Float>.size * 3 * 4, options: [.optionCPUCacheModeWriteCombined])
mVideoBackgroundTextureCoordinates = mMetalDevice.makeBuffer(length: MemoryLayout<Float>.size * 2 * 4, options: [.optionCPUCacheModeWriteCombined])
mVideoBackgroundIndices = mMetalDevice.makeBuffer(length: MemoryLayout<UInt16>.size * 6, options: [.optionCPUCacheModeWriteCombined])
}
/// Render the video background
func renderVideoBackground(encoder: MTLRenderCommandEncoder?, projectionMatrix: MTLBuffer, mesh: VuforiaMesh) {
// Copy mesh data into metal buffers
mVideoBackgroundVertices.contents().copyMemory(from: mesh.vertices, byteCount: MemoryLayout<Float>.size * Int(mesh.numVertices) * 3)
mVideoBackgroundTextureCoordinates.contents().copyMemory(from: mesh.textureCoordinates, byteCount: MemoryLayout<Float>.size * Int(mesh.numVertices) * 2)
mVideoBackgroundIndices.contents().copyMemory(from: mesh.indices, byteCount: MemoryLayout<CShort>.size * Int(mesh.numIndices))
// Set the render pipeline state
encoder?.setRenderPipelineState(mVideoBackgroundPipelineState)
// Set the vertex buffer
encoder?.setVertexBuffer(mVideoBackgroundVertices, offset: 0, index: 0)
// Set the projection matrix
encoder?.setVertexBuffer(projectionMatrix, offset: 0, index: 1)
// Set the texture coordinate buffer
encoder?.setVertexBuffer(mVideoBackgroundTextureCoordinates, offset: 0, index: 2)
encoder?.setFragmentSamplerState(mDefaultSamplerState, index: 0)
// Draw the geometry
encoder?.drawIndexedPrimitives(
type: .triangle,
indexCount: 6,
indexType: .uint16,
indexBuffer: mVideoBackgroundIndices,
indexBufferOffset: 0
)
}
}
extension MetalRenderer {
class func defaultSampler(device: MTLDevice) -> MTLSamplerState? {
let sampler = MTLSamplerDescriptor()
sampler.minFilter = .linear
sampler.magFilter = .linear
sampler.mipFilter = .linear
sampler.maxAnisotropy = 1
sampler.sAddressMode = .clampToEdge
sampler.tAddressMode = .clampToEdge
sampler.rAddressMode = .clampToEdge
sampler.normalizedCoordinates = true
sampler.lodMinClamp = 0
sampler.lodMaxClamp = .greatestFiniteMagnitude
return device.makeSamplerState(descriptor: sampler)
}
}
Adding code from the view that creates renderer:
import UIKit
import MetalKit
protocol VuforiaViewDelegate: AnyObject {
func renderFrame(vuforiaView: VuforiaView)
}
class VuforiaView: UIView {
weak var delegate: VuforiaViewDelegate?
var mVuforiaStarted = false
private var mConfigurationChanged = true
private var mRenderer: MetalRenderer!
private var mMetalDevice: MTLDevice!
private var mMetalCommandQueue: MTLCommandQueue!
private var mCommandExecutingSemaphore: DispatchSemaphore!
private var mDepthStencilState: MTLDepthStencilState!
private var mDepthTexture: MTLTexture!
private var mVideoBackgroundProjectionBuffer: MTLBuffer!
private lazy var metalLayer = layer as! CAMetalLayer
override class var layerClass: AnyClass { CAMetalLayer.self }
// Transformations and variables - constantly updated by vuforia frame updates
private var viewport = MTLViewport()
private var trackableProjection = matrix_float4x4()
private var trackableModelView = matrix_float4x4()
private var trackableScaledModelView = matrix_float4x4()
private(set) var worldOriginProjectionMatrix = matrix_float4x4()
private(set) var worldOriginModelViewMatrix = matrix_float4x4()
private(set) var targetPose = matrix_float4x4()
private(set) var targetSize = simd_float3()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
contentScaleFactor = UIScreen.main.nativeScale
// Get the system default metal device
mMetalDevice = MTLCreateSystemDefaultDevice()
// Metal command queue
mMetalCommandQueue = mMetalDevice.makeCommandQueue()
// Create a dispatch semaphore, used to synchronise command execution
mCommandExecutingSemaphore = DispatchSemaphore(value: 1)
// Create a CAMetalLayer and set its frame to match that of the view
let layer = self.layer as! CAMetalLayer
layer.device = mMetalDevice
layer.pixelFormat = .bgra8Unorm
layer.framebufferOnly = true
layer.contentsScale = contentScaleFactor
// Get the default library from the bundle (Metal shaders)
let library = mMetalDevice.makeDefaultLibrary()
// Create a depth texture that is needed when rendering the augmentation.
let screenSize = UIScreen.main.bounds.size
let depthTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(
pixelFormat: .depth32Float,
width: Int(screenSize.width * contentScaleFactor),
height: Int(screenSize.height * contentScaleFactor),
mipmapped: false
)
depthTextureDescriptor.usage = .renderTarget
mDepthTexture = mMetalDevice.makeTexture(descriptor: depthTextureDescriptor)
// Video background projection matrix buffer
mVideoBackgroundProjectionBuffer = mMetalDevice.makeBuffer(length: MemoryLayout<Float>.size * 16, options: [])
// Fragment depth stencil
let depthStencilDescriptor = MTLDepthStencilDescriptor()
depthStencilDescriptor.depthCompareFunction = .less
depthStencilDescriptor.isDepthWriteEnabled = true
mDepthStencilState = mMetalDevice.makeDepthStencilState(descriptor: depthStencilDescriptor)
mRenderer = MetalRenderer(
metalDevice: mMetalDevice,
layer: layer,
library: library,
textureDepth: mDepthTexture
)
}
private func configureVuforia() {
let orientationValue: Int32 = {
let orientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation ?? .portrait
switch orientation {
case .portrait: return 0
case .portraitUpsideDown: return 1
case .landscapeLeft: return 2
case .landscapeRight: return 3
case .unknown: return 4
#unknown default: return 4
}
}()
let screenSize = UIScreen.main.bounds.size
configureRendering(
Int32(screenSize.width * contentScaleFactor),
Int32(screenSize.height * contentScaleFactor),
orientationValue
)
}
#objc private func renderFrameVuforia() {
objc_sync_enter(self)
if mVuforiaStarted {
if mConfigurationChanged {
mConfigurationChanged = false
configureVuforia()
}
renderFrameVuforiaInternal()
delegate?.renderFrame(vuforiaView: self)
}
objc_sync_exit(self)
}
private func renderFrameVuforiaInternal() {
//Check if Camera is Started
guard isCameraStarted() else { return }
// ========== Set up ==========
var viewportsValue = Array(arrayLiteral: 0.0, 0.0, Double(metalLayer.drawableSize.width), Double(metalLayer.drawableSize.height), 0.0, 1.0)
// --- Command buffer ---
// Get the command buffer from the command queue
let commandBuffer = mMetalCommandQueue.makeCommandBuffer()
// Get the next drawable from the CAMetalLayer
let drawable = metalLayer.nextDrawable()
// It's possible for nextDrawable to return nil, which means a call to
// renderCommandEncoderWithDescriptor will fail
guard drawable != nil else { return }
// Wait for exclusive access to the GPU
let _ = mCommandExecutingSemaphore.wait(timeout: .distantFuture)
// -- Render pass descriptor ---
// Set up a render pass decriptor
let renderPassDescriptor = MTLRenderPassDescriptor()
// Draw to the drawable's texture
renderPassDescriptor.colorAttachments[0].texture = drawable?.texture
// Clear the colour attachment in case there is no video frame
renderPassDescriptor.colorAttachments[0].loadAction = .clear
// Store the data in the texture when rendering is complete
renderPassDescriptor.colorAttachments[0].storeAction = .store
// Use textureDepth for depth operations.
renderPassDescriptor.depthAttachment.texture = mDepthTexture
// Get a command encoder to encode into the command buffer
let encoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
if prepareToRender(
&viewportsValue,
UnsafeMutableRawPointer(Unmanaged.passRetained(mMetalDevice!).toOpaque()),
UnsafeMutableRawPointer(Unmanaged.passRetained(drawable!.texture).toOpaque()),
UnsafeMutableRawPointer(Unmanaged.passRetained(encoder!).toOpaque())
) {
viewport.originX = viewportsValue[0]
viewport.originY = viewportsValue[1]
viewport.width = viewportsValue[2]
viewport.height = viewportsValue[3]
viewport.znear = viewportsValue[4]
viewport.zfar = viewportsValue[5]
encoder?.setViewport(viewport)
// Once the camera is initialized we can get the video background rendering values
getVideoBackgroundProjection(mVideoBackgroundProjectionBuffer.contents())
// Call the renderer to draw the video background
mRenderer.renderVideoBackground(encoder: encoder, projectionMatrix: mVideoBackgroundProjectionBuffer, mesh: getVideoBackgroundMesh())
encoder?.setDepthStencilState(mDepthStencilState)
getOrigin(
&worldOriginProjectionMatrix.columns,
&worldOriginModelViewMatrix.columns
)
getImageTargetResult(
&trackableProjection.columns,
&trackableModelView.columns,
&trackableScaledModelView.columns,
&targetPose.columns,
&targetSize
)
}
// Pass Metal context data to Vuforia Engine (we may have changed the encoder since
// calling Vuforia::Renderer::begin)
finishRender(
UnsafeMutableRawPointer(Unmanaged.passRetained(drawable!.texture).toOpaque()),
UnsafeMutableRawPointer(Unmanaged.passRetained(encoder!).toOpaque())
)
// ========== Finish Metal rendering ==========
encoder?.endEncoding()
// Commit the rendering commands
// Command completed handler
commandBuffer?.addCompletedHandler { _ in self.mCommandExecutingSemaphore.signal() }
// Present the drawable when the command buffer has been executed (Metal
// calls to CoreAnimation to tell it to put the texture on the display when
// the rendering is complete)
commandBuffer?.present(drawable!)
// Commit the command buffer for execution as soon as possible
commandBuffer?.commit()
}
}
Another problem is that in portrait mode something is wrong with aspect ratio, camera background is drawn distorted. But this is for another subject.
Shaders.metal:
/*===============================================================================
Copyright (c) 2020, PTC Inc. All rights reserved.
Vuforia is a trademark of PTC Inc., registered in the United States and other
countries.
===============================================================================*/
#include <metal_stdlib>
using namespace metal;
// === Texture sampling shader ===
struct VertexTextureOut
{
float4 m_Position [[ position ]];
float2 m_TexCoord;
};
vertex VertexTextureOut texturedVertex(constant packed_float3* pPosition [[ buffer(0) ]],
constant float4x4* pMVP [[ buffer(1) ]],
constant float2* pTexCoords [[ buffer(2) ]],
uint vid [[ vertex_id ]])
{
VertexTextureOut out;
float4 in(pPosition[vid], 1.0f);
out.m_Position = *pMVP * in;
out.m_TexCoord = pTexCoords[vid];
return out;
}
fragment half4 texturedFragment(VertexTextureOut inFrag [[ stage_in ]],
texture2d<half> tex2D [[ texture(0) ]],
sampler sampler2D [[ sampler(0) ]])
{
return tex2D.sample(sampler2D, inFrag.m_TexCoord);
}
// === Uniform color shader ===
struct VertexOut
{
float4 m_Position [[ position ]];
};
vertex VertexOut uniformColorVertex(constant packed_float3* pPosition [[ buffer(0) ]],
constant float4x4* pMVP [[ buffer(1) ]],
uint vid [[ vertex_id ]])
{
VertexOut out;
float4 in(pPosition[vid], 1.0f);
out.m_Position = *pMVP * in;
return out;
}
fragment float4 uniformColorFragment(constant float4 &color [[ buffer(0) ]])
{
return color;
}
// === Vertex color shader ===
struct VertexColorOut
{
float4 m_Position [[ position ]];
float4 m_Color;
};
vertex VertexColorOut vertexColorVertex(constant packed_float3* pPosition [[ buffer(0) ]],
constant float4* pColor [[ buffer(1) ]],
constant float4x4* pMVP [[ buffer(2) ]],
uint vid [[ vertex_id ]])
{
VertexColorOut out;
float4 in(pPosition[vid], 1.0f);
out.m_Position = *pMVP * in;
out.m_Color = pColor[vid];
return out;
}
fragment float4 vertexColorFragment(VertexColorOut inFrag [[ stage_in ]])
{
return inFrag.m_Color;
}

Problems (wrong scale, aspect ratio) while rendering MTKTexture to MTKView after applying compute shaders

I am trying to process video frames from the camera using compute metal shaders and display it to the user. The problem is with displaying the modified frames. The output contains stacked copies of the processed frame with some of them clipped and they don't fill the screen completely.
P.S I am new to both iOS and metal
So far, I have identified variables that control this:
1. Number of Thread groups launched
2. MTKView's drawable size
3. sampling id in the metal shader
I have played around with these with no good result.
Below are the code and my output
The function that sets up the MTKView
func initMetalView() {
metalView = MTKView(frame: view.frame, device: metalDevice)
metalView.delegate = self
metalView.framebufferOnly = false
metalView.colorPixelFormat = .bgra8Unorm
metalView.autoResizeDrawable = false
metalView.drawableSize = CGSize(width: 1920, height: 1080)
metalView.layer.transform = CATransform3DMakeRotation(CGFloat(Float.pi),0.0,1.0,0.0)
view.insertSubview(metalView, at: 0)
}
The AVCaptureVideoDataOutputSampleBufferDelegate used to convert CMSampleBuffer to MTLTexture
extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
// sample buffer -> image buffer -> CoreVideo metal texture -> MTL texture
guard let cvImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
else { fatalError("can't get image buffer") }
var textureCache: CVMetalTextureCache?
guard CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, metalDevice, nil, &textureCache) == kCVReturnSuccess else { fatalError("cant create texture cache") }
let width = CVPixelBufferGetWidth(cvImageBuffer)
let height = CVPixelBufferGetHeight(cvImageBuffer)
var imageTexture: CVMetalTexture?
let result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache!, cvImageBuffer, nil, MTLPixelFormat.bgra8Unorm, width, height, 0, &imageTexture)
guard let unwrappedImageTexture = imageTexture,
result == kCVReturnSuccess
else { fatalError("failed to create texture from image") }
inputTexture = CVMetalTextureGetTexture(unwrappedImageTexture)
}
}
The MTKViewDelegate used to apply the shader on inputTexture and display the outputTexture to metalView
extension ViewController: MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let inputTexture = inputTexture,
let commandQueue = commandQueue,
let commandBuffer = commandQueue.makeCommandBuffer(),
let encoder = commandBuffer.makeComputeCommandEncoder(),
let pipelineState = pipelineState
else { return }
encoder.setComputePipelineState(pipelineState)
encoder.setTextures([metalView.currentDrawable!.texture, inputTexture], range: 0..<2)
encoder.dispatchThreadgroups(MTLSizeMake(inputTexture.width/16, inputTexture.height/16, 1), threadsPerThreadgroup: threadsPerBlock)
// inputTexture w:1920, h:1080
encoder.endEncoding()
commandBuffer.present(metalView.currentDrawable!)
commandBuffer.commit()
}
}
The metal compute shader
#include <metal_stdlib>
using namespace metal;
kernel void blacky (texture2d<float, access::write> outTexture [[texture(0)]],
texture2d<float, access::read> inTexture [[texture(1)]],
uint2 id [[thread_position_in_grid]]) {
uint2 flipped_id = uint2(id.y, id.x);
float3 val = inTexture.read(flipped_id).rgb;
float g = (val.r + val.g + val.b)/3.0;
float4 out = float4(g, g, g, 1);
outTexture.write(out.rgba, id);
}
You can see the current output here: https://i.imgur.com/hVDox3U

Why MTLTexture with 2D Array doesn't work?

I'm trying to replicate splat map technique from Unity tutorial. They use Texture2DArray so I created MTLTexture with this type:
private func createTerrainTexture(_ bundle: Bundle) -> MTLTexture {
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError()
}
let names = ["sand", "grass", "earth", "stone", "snow"]
let loader = MTKTextureLoader(device: device)
let array = names.map { name -> MTLTexture in
do {
return try loader.newTexture(name: name, scaleFactor: 1.0, bundle: bundle, options: nil)
} catch {
fatalError()
}
}
guard let queue = device.makeCommandQueue() else {
fatalError()
}
guard let commandBuffer = queue.makeCommandBuffer() else {
fatalError()
}
guard let encoder = commandBuffer.makeBlitCommandEncoder() else {
fatalError()
}
let descriptor = MTLTextureDescriptor()
descriptor.textureType = .type2DArray
descriptor.pixelFormat = array[0].pixelFormat
descriptor.width = array[0].width
descriptor.height = array[0].height
descriptor.mipmapLevelCount = array[0].mipmapLevelCount
descriptor.arrayLength = 5
guard let texture = device.makeTexture(descriptor: descriptor) else {
fatalError()
}
var slice = 0
array.forEach { item in
encoder.copy(from: item,
sourceSlice: 0,
sourceLevel: 0,
sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
sourceSize: MTLSize(width: item.width, height: item.height, depth: 1),
to: texture,
destinationSlice: slice,
destinationLevel: 0,
destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))
slice += 1
}
encoder.endEncoding()
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
return texture
}
Here is my fragment shader function:
fragment half4 terrainFragment(TerrainVertexOutput in [[stage_in]],
texture2d_array<float> terrainTexture [[texture(0)]])
{
constexpr sampler sampler2d(coord::normalized, filter::linear, address::repeat);
float2 uv = in.position.xz * 0.02;
float4 c1 = terrainTexture.sample(sampler2d, uv, 0);
return half4(c1);
}
Here is Unity shader from tutorial:
void surf (Input IN, inout SurfaceOutputStandard o) {
float2 uv = IN.worldPos.xz * 0.02;
fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MainTex, float3(uv, 0));
Albedo = c.rgb * _Color;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
For some reason I'm getting wrong result when texture repeated in columns.
Result I want to have is:
Update.
Here is how texture looks like in GPU Frame Debugger:
When I copy mipmaps like this:
var slice = 0
array.forEach { item in
print(item.width, item.height, item.mipmapLevelCount)
for i in 0..<descriptor.mipmapLevelCount {
encoder.copy(from: item,
sourceSlice: 0,
sourceLevel: i,
sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
sourceSize: MTLSize(width: item.width, height: item.height, depth: 1),
to: texture,
destinationSlice: slice,
destinationLevel: i,
destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))
}
slice += 1
}
I'm getting error:
-[MTLDebugBlitCommandEncoder validateCopyFromTexture:sourceSlice:sourceLevel:sourceOrigin:sourceSize:toTexture:destinationSlice:destinationLevel:destinationOrigin:options:]:254: failed assertion `(sourceOrigin.x + sourceSize.width)(512) must be <= width(256).'
Problem was in incorrect porting of fragment shader input variable. In original input worldPos was used, but I used float4 position [[position]] and according to Metal specification this means
Describes the window-relative coordinate (x, y, z, 1/w) values for the fragment.
So position was incorrect. Here is how correct fragment shader input looks like:
struct TerrainVertexOutput
{
float4 position [[position]];
float3 p;
};
And vertex function:
vertex TerrainVertexOutput terrainVertex(TerrainVertexInput in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant MyNodeBuffer& scn_node [[buffer(1)]])
{
TerrainVertexOutput v;
v.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
v.p = (scn_node.modelTransform * float4(in.position, 1.0)).xyz;
return v;
}

Implement CoreML Custom Layer With Two Inputs

I have a tensorflow graph that I want to convert to CoreML, but it uses some operations that are missing, which I will have to implement as Custom Layers.
The two operations I'm focussing on now are Sin and FloorDiv.
Sin was quite straightforward, I could follow this tutorial, and I have a working Swift class and Metal kernel that does the job, which I tested with a toy coreml file:
import Foundation
import CoreML
import Accelerate
#objc(Sin) class Sin: NSObject, MLCustomLayer {
let sinPipeline: MTLComputePipelineState
required init(parameters: [String : Any]) throws {
print(#function, parameters)
let sinFunction = GPUDispatch.sharedInstance.library.makeFunction(name: "sin")!
sinPipeline = try! GPUDispatch.sharedInstance.device.makeComputePipelineState(
function: sinFunction)
super.init()
}
func setWeightData(_ weights: [Data]) throws {
print(#function, weights)
}
func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws
-> [[NSNumber]] {
print(#function, inputShapes)
return inputShapes
}
func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws {
for i in 0..<inputs.count {
let input = inputs[i]
let output = outputs[i]
var count = Int32(input.count)
let iptr = UnsafeMutablePointer<Float>(OpaquePointer(input.dataPointer))
let optr = UnsafeMutablePointer<Float>(OpaquePointer(output.dataPointer))
vvsinf(optr, iptr, &count)
}
}
func encode(commandBuffer: MTLCommandBuffer,
inputs: [MTLTexture], outputs: [MTLTexture]) throws {
if let encoder = commandBuffer.makeComputeCommandEncoder() {
for i in 0..<inputs.count {
encoder.setTexture(inputs[i], index: 0)
encoder.setTexture(outputs[i], index: 1)
encoder.dispatch(pipeline: sinPipeline, texture: inputs[i])
encoder.endEncoding()
}
}
}
}
and in Sin.metal:
kernel void sin(
texture2d_array<half, access::read> inTexture [[texture(0)]],
texture2d_array<half, access::write> outTexture [[texture(1)]],
ushort3 gid [[thread_position_in_grid]])
{
if (gid.x >= outTexture.get_width() ||
gid.y >= outTexture.get_height()) {
return;
}
const float4 x = float4(inTexture.read(gid.xy, gid.z));
const float4 y = sin(x);
outTexture.write(half4(y), gid.xy, gid.z);
}
What I don't understand is how this would work if the custom layer has two inputs, such as I would require for FloorDiv, which returns floor(x / y).
How would I adapt the Sin class I provided to produce something like sin(x*y), even if it's just on the CPU? Are there any other good tutorials for this sort of thing?
The pattern is different than I expected, but is quite obvious now I've played with the code some more.
This is a class that implements FloorDiv:
import Foundation
import CoreML
import Accelerate
#objc(FloorDiv) class FloorDiv: NSObject, MLCustomLayer {
let floorDivPipeline: MTLComputePipelineState
required init(parameters: [String : Any]) throws {
print(#function, parameters)
let floorDivFunction = GPUDispatch.sharedInstance.library.makeFunction(name: "floordiv")!
floorDivPipeline = try! GPUDispatch.sharedInstance.device.makeComputePipelineState(
function: floorDivFunction)
super.init()
}
func setWeightData(_ weights: [Data]) throws {
print(#function, weights)
}
func outputShapes(forInputShapes inputShapes: [[NSNumber]]) throws
-> [[NSNumber]] {
print(#function, inputShapes)
return inputShapes
}
func evaluate(inputs: [MLMultiArray], outputs: [MLMultiArray]) throws {
let numerator = inputs[0]
let denominator = inputs[1]
var output = outputs[0]
assert(numerator.count == denominator.count)
var count = Int32(numerator.count)
let numerator_ptr = UnsafeMutablePointer<Float>(OpaquePointer(numerator.dataPointer))
let denominator_ptr = UnsafeMutablePointer<Float>(OpaquePointer(denominator.dataPointer))
let output_ptr = UnsafeMutablePointer<Float>(OpaquePointer(output.dataPointer))
vvdivf(output_ptr, numerator_ptr, denominator_ptr, &count)
vvfloorf(output_ptr, output_ptr, &count)
}
func encode(commandBuffer: MTLCommandBuffer,
inputs: [MTLTexture], outputs: [MTLTexture]) throws {
if let encoder = commandBuffer.makeComputeCommandEncoder() {
encoder.setTexture(inputs[0], index: 0)
encoder.setTexture(inputs[1], index: 1)
encoder.setTexture(outputs[0], index: 2)
encoder.dispatch(pipeline: floorDivPipeline, texture: inputs[0])
encoder.endEncoding()
}
}
}
And here is the Metal kernel:
#include <metal_stdlib>
using namespace metal;
kernel void floordiv(
texture2d_array<half, access::read> inTexture [[texture(0)]],
texture2d_array<half, access::read> inTexture2 [[texture(1)]],
texture2d_array<half, access::write> outTexture [[texture(2)]],
ushort3 gid [[thread_position_in_grid]])
{
if (gid.x >= outTexture.get_width() ||
gid.y >= outTexture.get_height()) {
return;
}
const float4 x = float4(inTexture.read(gid.xy, gid.z));
const float4 x2 = float4(inTexture2.read(gid.xy, gid.z));
const float4 y = floor(x / x2);
outTexture.write(half4(y), gid.xy, gid.z);
}

Large section of iPad OpenGL ES application not retina

I have an OpengGL ES app using a custom shader to display circles. See image below. (Opening it in a new window might be helpful)
If you look carefully you can see that the display seems to be non retina from about 50% of the width and about 75% of the height. This seems to be the case only on iPad 3 (clients device). Simulator and an other iPad Air 2 behave normally.
I used a the basic OpenGL ES game project bundled with XCode.
Update:
The pixelated areas are the ones highlighted in red:
Please also see closeup:
I must admit I do not know where to start debugging this,
since it only seems to bug on the given device.
Here is the code I used to setup the context.
func setup()
{
initTextures()
self.context = EAGLContext(api: .openGLES2)
if !(self.context != nil) {
print("Failed to create ES context")
}
let view = self.view as! GLKView
// Fix for "good" aspect ratio
var frameSize = view.frame.size
frameSize.height = frameSize.width / 1.43023255813953
view.frame.size = frameSize
// Should force the aspect ratio
print("-------------")
print("width \(view.frame.width) and height \(view.frame.height)")
print("aspect ratio w/h \(view.frame.width / view.frame.height)")
print("-------------")
view.context = self.context!
view.drawableColorFormat = .RGBA8888
view.drawableMultisample = .multisample4X
// Application specific code
self.setupGL()
}
Update
I am drawing the circles with a custom fragment shader:
precision highp float;
uniform vec4 iResolution; // z - texWidth, w - texHeight
uniform sampler2D textureUnit;
uniform sampler2D smallPointsTextureUnit;
uniform vec2 gridSize;
#define SMOOTH(r,R) (1.0-smoothstep(R-0.09,R+0.09, r))
#define black vec3(0.0)
#define white vec3(1.0)
float circle(vec2 st, in float _radius, float pct ){
float l = length(st - vec2(0.5));
return 1.-smoothstep(_radius-(_radius*0.005) * pct,
_radius+(_radius*0.005),
l);
}
float stroke(vec2 uv, vec2 center, float radius, float width)
{
float dist = length(uv-center);
float t = 1.0 + smoothstep(radius, radius+width, dist)
- smoothstep(radius-width, radius, dist);
return t;
}
void main()
{
vec2 resolution = vec2(iResolution.x, iResolution.y);
vec2 uv = gl_FragCoord.xy;
vec2 st = gl_FragCoord.xy/resolution;
float colWidth = iResolution.x / gridSize.x;
float rowHeight = (iResolution.y + 1.0) / gridSize.y;
float smallerSize = min(rowHeight, colWidth);
float largerSize = max(rowHeight, colWidth);
vec2 divider = resolution / smallerSize;
st.x *= divider.x;
st.y *= divider.y;
float pct = largerSize / smallerSize;
float texXPos = (floor(st.x * smallerSize / largerSize) + 0.5) / iResolution.z;
float texYPos = (floor(gridSize.y -st.y) + 0.5) / iResolution.w;
vec4 tex = texture2D(textureUnit, vec2(
texXPos,
texYPos));
vec4 texSmallPoints = texture2D(smallPointsTextureUnit, vec2((floor(st.x * 2.0 * smallerSize / largerSize) + 0.5) / 128.0,
(floor(gridSize.y * 2.0 -st.y * 2.0) + 0.5) / 128.0));
//texSmallPoints.r = 0.5;
vec3 fillColor = vec3(tex.x, tex.y, tex.z);
st.x = mod(st.x, pct);
st.x = step( fract(st.x * 1.0 / pct), 1.0 / pct) * fract(st.x);
st.x *= texSmallPoints.r * 2.0; // subdivide for small circles
st.x = fract(st.x);
// Divide by 4
st.y *= texSmallPoints.r * 2.0;
st.y = fract(st.y);
//float r = 0.425;
float r = 0.4;
float fillPct = circle(st, r, 1.0);
vec2 center = vec2(0.5);
float strokePct = stroke(st, center, r, 0.032 * texSmallPoints.r * 1.8);
vec3 finalColor = vec3(1.0);
vec3 strokeColor = fillColor;
// todo -refactor if slow
// todo - invert
if (tex.a > 0.99) {
strokeColor = black;
}
if (tex.a < 0.01) {
strokeColor = white;
}
finalColor = mix(white, fillColor, fillPct);
finalColor = mix(finalColor, strokeColor, 1. - strokePct);
gl_FragColor = vec4(finalColor, 1.0);
}
And GLKViewController:
//
// HomeOpenGLController.swift
// Kobi
//
// Created by Tibor Udvari on 14/06/16.
// Copyright © 2016 Tibor Udvari. All rights reserved.
//
import GLKit
import OpenGLES
import HEXColor
open class KobiOpenGLControllerBase: GLKViewController
{
// --- Small points texture ---
var gpuSmallColorsTexture = [GLubyte](repeating: 0, count: 0)
var currentSmallPointTextureData: [GLubyte]? = nil
// - allocated size
let smallPointsTextureWidth = 128
let smallPointsTextureHeight = 128
// --- Color texture ---
var gpuColorsTexture = [GLubyte](repeating: 0, count: 0)
var currentColorsTextureData: [GLubyte]? = nil // size of grid
// - allocated size
let texWidth: Int = 256
let texHeight: Int = 256
// Grid - circles
let cols = 31
let rows = 22
open let maxIdx: Int
open let circleCount: Int
// Grid - pixels
var width: CGFloat = 0.0
var height: CGFloat = 0.0
var circleWidth: CGFloat = 0.0
var circleHeight: CGFloat = 0.0
// OpenGL
var program: GLuint = 0
var circleProgram: GLuint = 0
var context: EAGLContext? = nil
required public init?(coder aDecoder: NSCoder) {
maxIdx = cols * rows
circleCount = cols * rows
super.init(coder: aDecoder)
}
// 0 is positive instead of 0
func sign(_ x: Int) -> Int {
let r = x < 0 ? -1 : 1
return r
}
// MARK: - Setup
override open func viewDidLoad() {
setupGridData()
}
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setup() // because width and height is not initiated yet
}
func setupGridData(){
//currentSmallPointTextureData = createCurrentSmallPointsTextureData()
}
func setup()
{
initTextures()
self.context = EAGLContext(api: .openGLES2)
if !(self.context != nil) {
print("Failed to create ES context")
}
let view = self.view as! GLKView
// Fix for "good" aspect ratio
var frameSize = view.frame.size
frameSize.height = frameSize.width / 1.43023255813953
view.frame.size = frameSize
// Should force the aspect ratio
print("-------------")
print("width \(view.frame.width) and height \(view.frame.height)")
print("aspect ratio w/h \(view.frame.width / view.frame.height)")
print("-------------")
view.context = self.context!
view.drawableColorFormat = .RGBA8888
view.drawableMultisample = .multisample4X
//view.drawableMultisample = .MultisampleNone
//view.multipleTouchEnabled = true
width = self.view.frame.size.width * self.view.contentScaleFactor
height = self.view.frame.size.height * self.view.contentScaleFactor
circleWidth = width / CGFloat(cols)
circleHeight = height / CGFloat(rows)
self.setupGL()
}
func initTextures()
{
gpuColorsTexture = [GLubyte](repeating: 0, count: Int(texWidth)*Int(texHeight)*4)
gpuSmallColorsTexture = [GLubyte](repeating: 128, count: Int(smallPointsTextureWidth)*Int(smallPointsTextureHeight))
}
// MARK: - GLKView and GLKViewController delegate methods
func sendTexturesToGPU() {
for i in 0..<currentColorsTextureData!.count / 4 {
let r = Int(i) / Int(cols)
let c = Int(i) % cols
let j = r * texWidth + c
gpuColorsTexture[j*4] = currentColorsTextureData![i * 4]; //= GLubyte(255); // red
gpuColorsTexture[j*4+1] = currentColorsTextureData![i * 4 + 1]; //GLubyte(random() % 255); // green
gpuColorsTexture[j*4+2] = currentColorsTextureData![i * 4 + 2]; //GLubyte(0); // blue
gpuColorsTexture[j*4+3] = currentColorsTextureData![i * 4 + 3]; // used for the stroke color
}
for i in 0..<currentSmallPointTextureData!.count{
let r = Int(i) / Int(31 * 2)
let c = Int(i) % (31 * 2)
let j = r * 128 + c
gpuSmallColorsTexture[j] = currentSmallPointTextureData![i];
}
glActiveTexture(GLenum(GL_TEXTURE1));
glTexImage2D(GLenum(GL_TEXTURE_2D), GLint(0), GL_LUMINANCE, GLsizei(smallPointsTextureWidth), GLsizei(smallPointsTextureHeight), GLint(0), GLenum(GL_LUMINANCE), GLenum(GL_UNSIGNED_BYTE), &gpuSmallColorsTexture)
glActiveTexture(GLenum(GL_TEXTURE0));
glTexImage2D(GLenum(GL_TEXTURE_2D), GLint(0), GL_RGBA, GLsizei(texWidth), GLsizei(texHeight), GLint(0), GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), &gpuColorsTexture);
}
func update() {
print("update")
//todo
}
// todo send a uniform array
override open func glkView(_ view: GLKView, drawIn rect: CGRect) {
glClearColor(1.0, 1.0, 0.0, 1.0)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
glEnable(GLenum(GL_DEPTH_TEST))
glEnable(GLenum(GL_POINT_SIZE));
glEnable(GLenum(GL_BLEND))
glBlendFunc(GLenum(GL_SRC_ALPHA), GLenum(GL_ONE_MINUS_SRC_ALPHA))
glEnable(GLenum(GL_POINT_SMOOTH))
// 22 x 15
var baseModelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, 0.0)
baseModelViewMatrix = GLKMatrix4Rotate(baseModelViewMatrix, 0.0, 0.0, 1.0, 0.0)
var modelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, 1.5)
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, 0.0, 1.0, 1.0, 1.0)
modelViewMatrix = GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix)
modelViewMatrix = GLKMatrix4Identity
glUseProgram(program)
/*
withUnsafePointer(to: &modelViewProjectionMatrix, {
$0.withMemoryRebound(to: Float.self, capacity: 16, {
glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, $0)
})
})*/
withUnsafePointer(to: &modelViewMatrix, {
$0.withMemoryRebound(to: Float.self, capacity: 16, {
glUniformMatrix4fv(glGetUniformLocation(program, "modelViewProjectionMatrix"), 1, 0, UnsafePointer($0))
})
})
glVertexAttribPointer(0, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), 0, squareVertices)
glUniform4f(glGetUniformLocation(program, "iResolution"), Float(width), Float(height), Float(texWidth), Float(texHeight))
glUniform2f(glGetUniformLocation(program, "gridSize"), Float(cols), Float(rows))
glDrawArrays(GLenum(GL_TRIANGLE_STRIP) , 0, 4)
glUseProgram(circleProgram)
}
// MARK: - Texture
func setupTextures()
{
let texInfo = try! GLKTextureLoader.texture(with: UIImage(named: "texture256")!.cgImage!, options: nil)
glActiveTexture(GLenum(GL_TEXTURE0))
glBindTexture(GLenum(GL_TEXTURE0), (texInfo.name))
//var dataTexture = (texInfo.name)
glUniform1i(glGetUniformLocation(program, "textureUnit"), 0)
glActiveTexture(GLenum(GL_TEXTURE1))
let _ = createSmallPointsTexture()
glUniform1i(glGetUniformLocation(program, "smallPointsTextureUnit"), 1)
}
func createSmallPointsTexture() -> GLuint {
var texture: GLuint = 1
glGenTextures(GLsizei(1), &texture)
glBindTexture(GLenum(GL_TEXTURE_2D), texture)
glActiveTexture(texture)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR);
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE);
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE);
glGenerateMipmap(GLenum(GL_TEXTURE_2D));
return texture
}
// MARK: - OpenGL ES 2 shader compilation
func setupGL() {
EAGLContext.setCurrent(self.context)
let _ = self.loadShaders()
glUseProgram(program)
glEnableVertexAttribArray(0)
self.setupTextures()
}
func tearDownGL() {
EAGLContext.setCurrent(self.context)
if program != 0 {
glDeleteProgram(program)
program = 0
}
}
func loadShaders() -> Bool {
var vertShader: GLuint = 0
var fragShader: GLuint = 0
var vertShaderPathname: String
var fragShaderPathname: String
// Create shader program.
program = glCreateProgram()
// Create and compile vertex shader.
vertShaderPathname = Bundle.main.path(forResource: "Shader", ofType: "vsh")!
if self.compileShader(&vertShader, type: GLenum(GL_VERTEX_SHADER), file: vertShaderPathname) == false {
print("Failed to compile vertex shader")
return false
}
// Create and compile fragment shader.
fragShaderPathname = Bundle.main.path(forResource: "Shader", ofType: "fsh")!
if !self.compileShader(&fragShader, type: GLenum(GL_FRAGMENT_SHADER), file: fragShaderPathname) {
print("Failed to compile fragment shader")
/*
var fragInfoLength: GLint = 0
glGetShaderiv(fragShader, GLenum(GL_INFO_LOG_LENGTH), &fragInfoLength)
//let cstring = UnsafeMutablePointer<GLchar>(allocatingCapacity: Int(fragInfoLength))
var cstring = UnsafeMutablePointer<GLchar>(malloc(Int(fragInfoLength)))
glGetShaderInfoLog(fragShader, fragInfoLength, nil, cstring)
let shaderInfoLog = NSString(utf8String: cstring)
print(shaderInfoLog)
*/
return false
}
// Attach vertex shader to program.
glAttachShader(program, vertShader)
// Attach fragment shader to program.
glAttachShader(program, fragShader)
// Bind attribute locations.
// This needs to be done prior to linking.
glBindAttribLocation(program, 0, "position")
// Link program.
if !self.linkProgram(program) {
print("Failed to link program: \(program)")
if vertShader != 0 {
glDeleteShader(vertShader)
vertShader = 0
}
if fragShader != 0 {
glDeleteShader(fragShader)
fragShader = 0
}
if program != 0 {
glDeleteProgram(program)
program = 0
}
return false
}
// Release vertex and fragment shaders.
if vertShader != 0 {
glDetachShader(program, vertShader)
glDeleteShader(vertShader)
}
if fragShader != 0 {
glDetachShader(program, fragShader)
glDeleteShader(fragShader)
}
return true
}
func compileShader(_ shader: inout GLuint, type: GLenum, file: String) -> Bool {
var status: GLint = 0
var source: UnsafePointer<Int8>
do {
source = try NSString(contentsOfFile: file, encoding: String.Encoding.utf8.rawValue).utf8String!
} catch {
print("Failed to load vertex shader")
return false
}
//var castSource = UnsafePointer<GLchar>(source)
var castSource: UnsafePointer<GLchar>? = UnsafePointer<GLchar>(source)
shader = glCreateShader(type)
glShaderSource(shader, 1, &castSource, nil)
glCompileShader(shader)
var logLength: GLint = 0
glGetShaderiv(shader, GLenum(GL_INFO_LOG_LENGTH), &logLength)
if logLength > 0 {
//var log = UnsafeMutablePointer<GLchar>(malloc(Int(logLength)))
print("Log length gt 0")
/*
var log = UnsafeMutablePointer<GLchar>(malloc(Int(logLength)))
glGetShaderInfoLog(shader, logLength, &logLength, log)
NSLog("Shader compile log: \n%s", log)
free(log)
*/
}
glGetShaderiv(shader, GLenum(GL_COMPILE_STATUS), &status)
if status == 0 {
glDeleteShader(shader)
return false
}
return true
}
func linkProgram(_ prog: GLuint) -> Bool {
var status: GLint = 0
glLinkProgram(prog)
//#if defined(DEBUG)
// var logLength: GLint = 0
// glGetShaderiv(shader, GLenum(GL_INFO_LOG_LENGTH), &logLength)
// if logLength > 0 {
// var log = UnsafeMutablePointer<GLchar>(malloc(Int(logLength)))
// glGetShaderInfoLog(shader, logLength, &logLength, log)
// NSLog("Shader compile log: \n%s", log)
// free(log)
// }
//#endif
glGetProgramiv(prog, GLenum(GL_LINK_STATUS), &status)
if status == 0 {
return false
}
return true
}
func validateProgram(_ prog: GLuint) -> Bool {
var logLength: GLsizei = 0
var status: GLint = 0
glValidateProgram(prog)
glGetProgramiv(prog, GLenum(GL_INFO_LOG_LENGTH), &logLength)
if logLength > 0 {
var log: [GLchar] = [GLchar](repeating: 0, count: Int(logLength))
glGetProgramInfoLog(prog, logLength, &logLength, &log)
print("Program validate log: \n\(log)")
}
glGetProgramiv(prog, GLenum(GL_VALIDATE_STATUS), &status)
var returnVal = true
if status == 0 {
returnVal = false
}
return returnVal
}
// MARK : Cleanup
override open func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
if self.isViewLoaded && (self.view.window != nil) {
self.view = nil
self.tearDownGL()
if EAGLContext.current() === self.context {
EAGLContext.setCurrent(nil)
}
self.context = nil
}
}
deinit {
self.tearDownGL()
if EAGLContext.current() === self.context {
EAGLContext.setCurrent(nil)
}
}
}
var squareVertices: [GLfloat] = [
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
1.0, 1.0,
];
Thank you for updating. I am not sure exactly which part caused it but I am sure it must happen in your fragment shader code.
I guess vec2 st's value changing is not steady there while it is being calculated.
I want you to test this to see if it is your fragment shader.
just draw only 1 circle without uniform values except iResolution.
only using iResolution and gl_FragCoord, draw a circle.
I think it's going to properly show up. Then go over your FS.

Resources