Skip to content

Instantly share code, notes, and snippets.

@xhjkl
Last active April 24, 2020 03:09
Show Gist options
  • Save xhjkl/6e9e58428d51b0a5c92f6f83d648effa to your computer and use it in GitHub Desktop.
Save xhjkl/6e9e58428d51b0a5c92f6f83d648effa to your computer and use it in GitHub Desktop.
OpenGL wrapper for Swift

Draw.swift is convenience wrapper for OpenGL. It exports a global draw which provides an interface for the default rendering device.

First, create your pipelines as you normally would:

  let line = try! draw.makePipeline(...)

Then, choose a destination framestore and a pipeline to render with:

  draw.setTarget(someOffscreenTexture, width: width, height: height)
  draw.with(motionBlurPipeline) {
    draw.uniform(...)
    draw.attributeLayout(...)
    draw.drawStrip(vertexCount: 4)
  }

The framestore texture is cleared upon setting it as the destination. To render to the screen, use nil in place of the framestore argument:

  draw.setTarget(nil, width: screenWidth, height: screenHeight)

Enjoy responsibly.

//
// Convenience extensions to read images into textures.
//
import UIKit
import OpenGLES
extension DrawContext {
/// Make texture with an image in it.
public func makeTexture(id: TextureId? = nil, width: Int? = nil, height: Int? = nil, fromFileContents filename: String) -> TextureId {
let id = id ?? allocateTextures(count: 1)[0]
let uiimage = UIImage(named: "Assets/\(filename)")!
let cgimage = uiimage.cgImage!
let (width, height) = (width ?? cgimage.width, height ?? cgimage.height)
let bitsPerComponent = 8
let bytesPerRow = 4 * width
let bytesCount = bytesPerRow * height
var storage = ContiguousArray<UInt8>(repeating: 0, count: bytesCount)
storage.withUnsafeMutableBytes { rawData in
let cgcontext = CGContext(
data: rawData.baseAddress,
width: width, height: height,
bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
)!
let coverAll = CGRect(0, 0, CGFloat(width), CGFloat(height))
cgcontext.clear(coverAll)
cgcontext.scaleBy(x: 1, y: -1)
cgcontext.translateBy(x: 0, y: CGFloat(-height))
cgcontext.draw(cgimage, in: coverAll)
glBindTexture(GLenum(GL_TEXTURE_2D), id)
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)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)
glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), rawData.baseAddress)
glBindTexture(GLenum(GL_TEXTURE_2D), 0)
}
return id
}
}
//
// Convenience extensions to load shader source code from files.
//
import Foundation
enum GLExternalResourceError: Error {
case FileAccessFailure
}
extension DrawContext {
/// Create a pipeline from a pair of shader files.
public func makePipeline(
vertexSourceFromContentsOfFile vertexFile: String,
fragmentSourceFromContentsOfFile fragmentFile: String,
attributeOrdering: [String: Int],
uniformOrdering: [String: Int],
use capabilities: GraphicalCapabilities = [],
emitWarningsWith warnCallback: ((String) -> ())? = nil
) throws -> Pipeline {
let maybeVertexSourcePath = Bundle.main.path(forResource: "Shaders/\(vertexFile)", ofType: "vert")
let maybeFragmentSourcePath = Bundle.main.path(forResource: "Shaders/\(fragmentFile)", ofType: "frag")
guard
let vertexSourcePath = maybeVertexSourcePath,
let fragmentSourcePath = maybeFragmentSourcePath
else {
throw GLExternalResourceError.FileAccessFailure
}
let vertexSource = try String(contentsOfFile: vertexSourcePath)
let fragmentSource = try String(contentsOfFile: fragmentSourcePath)
return try makePipeline(vertexSource: vertexSource, fragmentSource: fragmentSource, attributeOrdering: attributeOrdering, uniformOrdering: uniformOrdering, use: capabilities, emitWarningsWith: warnCallback)
}
}
//
// Draw
//
// A sugar coating on top of OpenGL
// that encapsulates resource management,
// shader invocations, and multipass rendering
// in the notion of a context.
//
import OpenGLES
/// What could go wrong.
enum GLError: Error {
case vertexShaderCompilationFailure(String)
case fragmentShaderCompilationFailure(String)
case programLinkageFailure(String)
}
/// Resource handle.
public typealias TextureId = GLuint
public typealias BufferId = GLuint
/// How often does a buffer change.
public enum BufferUsageMode {
/// Not often.
case `static`
/// Once per a couple of frames.
case `dynamic`
/// Once per frame or more often.
case stream
}
/// What stages to enable during a draw.
public struct GraphicalCapabilities: OptionSet {
public typealias RawValue = Int
public let rawValue: RawValue
public init(rawValue: RawValue) {
self.rawValue = rawValue
}
public static let allDefault = GraphicalCapabilities([])
public static let blend = GraphicalCapabilities(rawValue: 1 << 2)
public static let depthTest = GraphicalCapabilities(rawValue: 1 << 3)
}
/// Instructions for pulling vertices from buffers.
public enum VertexInputType {
case float32(UInt)
}
public typealias AttributeLayoutDescriptor = (type: VertexInputType, stride: UInt, offset: UInt, divisor: UInt, source: BufferId)
public typealias AttributeLayoutId = GLuint
/// Compositional breakdown of a texel.
public enum TextureFormat {
/// Each texel has a single alpha component in range [0..1]; all colors are always zero.
case alpha
/// Each texel has red, green, and blue channels in range [0..1]; alpha is always one.
case rgb
/// Each texel has red, green, blue and alpha channels all in range [0..1].
case rgba
/// Each texel represents a luminance value in [0..1], which is repeated for rgb components of `FragColor`; alpha is always one.
case luminance
/// Each texel has a luminance and an alpha component.
case luminanceAlpha
}
/// Underlying storage for a texel.
public enum TextureDataType {
case uint8
case halfFloat
}
/// How to combine newly drawn color with already existing data in the destination.
public enum BlendFactorSet {
/// Final alpha is one minus destination alpha plus source alpha, which is default.
case mixBoth
/// Discard destination as if the source alpha was always equal to one.
case sourceOnly
}
/// All necessary state to perform a draw call.
public class Pipeline {
internal let programId: GLuint
internal let uniformIdMap: Dictionary<Int, GLint>
internal let requiredCapabilities: GraphicalCapabilities
fileprivate init(programId: GLuint, uniformIdMap: Dictionary<Int, GLint>, requiredCapabilities: GraphicalCapabilities) {
self.programId = programId
self.uniformIdMap = uniformIdMap
self.requiredCapabilities = requiredCapabilities
}
deinit {
glDeleteProgram(programId)
}
}
/// Command buffer and the volatile state associated with a rendering pipeline.
public class RenderPass {
fileprivate weak var pipeline: Pipeline!
fileprivate init(pipeline: Pipeline) {
self.pipeline = pipeline
}
/// Set vertex input.
public func attributeLayout(_ layout: AttributeLayoutId) {
glBindVertexArrayOES(layout)
}
/// Set an integer uniform.
public func uniform(_ id: Int, integer value: Int32) {
let glid = pipeline.uniformIdMap[id]!
glUniform1i(glid, value)
}
/// Set a fractional uniform.
public func uniform(_ id: Int, _ value: Float32) {
let glid = pipeline.uniformIdMap[id]!
glUniform1f(glid, value)
}
/// Set a vector uniform.
public func uniform(_ id: Int, vector2 value: Vector2) {
let glid = pipeline.uniformIdMap[id]!
glUniform2f(glid, value.x, value.y)
}
public func uniform(_ id: Int, vector3 value: Vector3) {
let glid = pipeline.uniformIdMap[id]!
glUniform3f(glid, value.x, value.y, value.z)
}
public func uniform(_ id: Int, vector4 value: Vector4) {
let glid = pipeline.uniformIdMap[id]!
glUniform4f(glid, value.x, value.y, value.z, value.w)
}
/// Set a matrix uniform.
public func uniform(_ id: Int, matrix2 value: Matrix2) {
let glid = pipeline.uniformIdMap[id]!
var value = value
withUnsafeBytes(of: &value) {
glUniformMatrix2fv(glid, 1, GLboolean(GL_FALSE), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
public func uniform(_ id: Int, matrix3 value: Matrix3) {
let glid = pipeline.uniformIdMap[id]!
var value = value
withUnsafeBytes(of: &value) {
glUniformMatrix3fv(glid, 1, GLboolean(GL_FALSE), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
public func uniform(_ id: Int, matrix4 value: Matrix4) {
let glid = pipeline.uniformIdMap[id]!
var value = value
withUnsafeBytes(of: &value) {
glUniformMatrix4fv(glid, 1, GLboolean(GL_FALSE), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
/// Set an array of vectors uniform.
public func uniform(_ id: Int, vector2Array values: [Vector2]) {
let glid = pipeline.uniformIdMap[id]!
values.withUnsafeBytes {
glUniform2fv(glid, GLsizei(values.count), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
public func uniform(_ id: Int, vector3Array values: [Vector3]) {
let glid = pipeline.uniformIdMap[id]!
values.withUnsafeBytes {
glUniform3fv(glid, GLsizei(values.count), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
public func uniform(_ id: Int, vector4Array values: [Vector4]) {
let glid = pipeline.uniformIdMap[id]!
values.withUnsafeBytes {
glUniform4fv(glid, GLsizei(values.count), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
/// Set an array of matrices uniform.
public func uniform(_ id: Int, matrix2Array values: [Matrix2]) {
let glid = pipeline.uniformIdMap[id]!
values.withUnsafeBytes {
glUniformMatrix2fv(glid, GLsizei(values.count), GLboolean(GL_FALSE), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
public func uniform(_ id: Int, matrix3Array values: [Matrix3]) {
let glid = pipeline.uniformIdMap[id]!
values.withUnsafeBytes {
glUniformMatrix3fv(glid, GLsizei(values.count), GLboolean(GL_FALSE), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
public func uniform(_ id: Int, matrix4Array values: [Matrix4]) {
let glid = pipeline.uniformIdMap[id]!
values.withUnsafeBytes {
glUniformMatrix4fv(glid, GLsizei(values.count), GLboolean(GL_FALSE), $0.baseAddress!.assumingMemoryBound(to: GLfloat.self))
}
}
/// Set input texture.
public func texture(_ id: TextureId) {
glBindTexture(GLenum(GL_TEXTURE_2D), id)
}
/// Set input texture for the specified texture unit.
public func texture(_ id: TextureId, at slot: Int) {
glActiveTexture(GLenum(GL_TEXTURE0 + Int32(slot)))
glBindTexture(GLenum(GL_TEXTURE_2D), id)
glActiveTexture(GLenum(GL_TEXTURE0))
}
/// Erase contents of the target.
public func clear() {
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
}
/// Schedule a draw call with Triangle Strip mode.
public func drawStrip(vertexCount: Int) {
glDrawArrays(GLenum(GL_TRIANGLE_STRIP), 0, GLsizei(vertexCount))
}
/// Schedule an instanced draw call with Triangle Strip mode.
public func drawStrip(vertexCount: Int, instanceCount: Int) {
glDrawArraysInstancedEXT(GLenum(GL_TRIANGLE_STRIP), 0, GLsizei(vertexCount), GLsizei(instanceCount))
}
}
/// Rendering context associated to a virtual device
public class DrawContext {
/// Which framestore was bound initially, before our reconfigurations took place.
///
/// Some platforms do not allow userland apps to render into zeroth
/// framestore, and ask them to render into some other predefined target.
/// Thus, we remember the original binding to restore it to render on the screen.
///
public internal(set) lazy var defaultFramebuffer = ({ () -> GLuint in
var fbid = GLint(0)
glGetIntegerv(GLenum(GL_FRAMEBUFFER_BINDING), &fbid)
return GLuint(fbid)
}())
/// We create framebuffers automatically the first time a texture is used as a rendering target.
public internal(set) var framebufferForTexture = [:] as [TextureId: GLuint]
fileprivate init() {
}
/// Provide a Draw context with a separate underlying GL context.
public final class func makeSeparate() -> DrawContext! {
return nil
}
/// Reserve id slots.
public func allocateTextures(count: Int) -> [TextureId] {
var array = Array<GLuint>(repeating: 0, count: count)
array.withUnsafeMutableBufferPointer {
glGenTextures(GLsizei(count), $0.baseAddress)
}
return array
}
/// Reserve id slots.
public func allocateBuffers(count: Int) -> [BufferId] {
var array = Array<GLuint>(repeating: 0, count: count)
array.withUnsafeMutableBufferPointer {
glGenBuffers(GLsizei(count), $0.baseAddress)
}
return array
}
/// Reserve storage for attribute data.
public func makeBuffer(id: BufferId? = nil, size: Int, usage: BufferUsageMode) -> BufferId {
let id = id ?? allocateBuffers(count: 1)[0]
resizeBuffer(id: id, size: size, usage: usage)
return id
}
/// Reallocate storage for attribute data.
public func resizeBuffer(id: BufferId, size: Int, usage: BufferUsageMode) {
let glusage: GLenum
switch usage {
case .static:
glusage = GLenum(GL_STATIC_DRAW)
case .dynamic:
glusage = GLenum(GL_DYNAMIC_DRAW)
case .stream:
glusage = GLenum(GL_STREAM_DRAW)
}
glBindBuffer(GLenum(GL_ARRAY_BUFFER), id)
glBufferData(GLenum(GL_ARRAY_BUFFER), size, nil, glusage)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)
}
/// Fill in the buffer with the specified bytes.
public func assign(to id: BufferId, data: [Float32]) {
assert(MemoryLayout<Float32>.size == MemoryLayout<GLfloat>.size, "floats should be of exact same depth between GL and the app code")
glBindBuffer(GLenum(GL_ARRAY_BUFFER), id)
glBufferSubData(GLenum(GL_ARRAY_BUFFER), 0, data.count * MemoryLayout<Float32>.stride, data)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)
}
public func assign(to id: BufferId, integerData data: [Int32]) {
assert(MemoryLayout<Int32>.size == MemoryLayout<GLint>.size, "ints should be of exact same depth between GL and the app code")
glBindBuffer(GLenum(GL_ARRAY_BUFFER), id)
glBufferSubData(GLenum(GL_ARRAY_BUFFER), 0, data.count * MemoryLayout<Int32>.stride, data)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)
}
public func assign(to id: BufferId, bytes: UnsafeRawBufferPointer) {
assert(MemoryLayout<Float32>.size == MemoryLayout<GLfloat>.size, "floats should be of exact same depth between GL and the app code")
glBindBuffer(GLenum(GL_ARRAY_BUFFER), id)
glBufferSubData(GLenum(GL_ARRAY_BUFFER), 0, bytes.count, bytes.baseAddress)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)
}
/// Reserve storage for texels.
public func makeTexture(id: TextureId? = nil, format: TextureFormat = .rgba, width: Int = 0, height: Int = 0) -> TextureId {
let id = id ?? allocateTextures(count: 1)[0]
glBindTexture(GLenum(GL_TEXTURE_2D), id)
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)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)
glBindTexture(GLenum(GL_TEXTURE_2D), 0)
resizeTexture(id: id, format: format, width: width, height: height)
return id
}
/// Resize a texture.
public func resizeTexture(id: TextureId, dataType: TextureDataType = .uint8, format: TextureFormat = .rgba, width: Int, height: Int) {
let glDataType: GLenum
switch dataType {
case .uint8:
glDataType = GLenum(GL_UNSIGNED_BYTE)
case .halfFloat:
glDataType = GLenum(GL_HALF_FLOAT_OES)
}
let glInternalPixelFormat: GLint
switch format {
case .alpha:
glInternalPixelFormat = GL_ALPHA
case .rgb:
glInternalPixelFormat = GL_RGB
case .rgba:
glInternalPixelFormat = GL_RGBA
case .luminance:
glInternalPixelFormat = GL_LUMINANCE
case .luminanceAlpha:
glInternalPixelFormat = GL_LUMINANCE_ALPHA
}
let glPixelFormat = GLenum(glInternalPixelFormat)
glBindTexture(GLenum(GL_TEXTURE_2D), id)
glTexImage2D(GLenum(GL_TEXTURE_2D), 0, glInternalPixelFormat, GLsizei(width), GLsizei(height), 0, glPixelFormat, glDataType, nil)
glBindTexture(GLenum(GL_TEXTURE_2D), 0)
}
/// Set a color to clear with.
public func setClearColor(r: Float32, g: Float32, b: Float32, a: Float32) {
glClearColor(r, g, b, a)
}
/// Set rendering target, but do not clear it.
///
/// textureId -- where to draw to
/// x, y, width, height -- area affected by framgent operaions
///
public func setTargetDirty(_ textureId: TextureId?, x: Int = 0, y: Int = 0, width: Int, height: Int) {
// Ensure `defaultFramebuffer` is obtained prior to the first framebuffer binding.
_ = defaultFramebuffer
// Nil `textureId` sets destination to default.
guard let textureId = textureId else {
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), defaultFramebuffer)
glViewport(GLint(x), GLint(y), GLsizei(width), GLsizei(height))
return
}
// Associate a framebuffer to this texture if it has not been used as a target.
guard let existingFramebuffer = framebufferForTexture[textureId] else {
var fb = GLuint(0)
glGenFramebuffersOES(1, &fb)
framebufferForTexture[textureId] = fb
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), fb)
glFramebufferTexture2D(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_TEXTURE_2D), textureId, 0)
glViewport(GLint(x), GLint(y), GLsizei(width), GLsizei(height))
return
}
// Or, reuse an existing framebuffer.
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), existingFramebuffer)
glViewport(GLint(x), GLint(y), GLsizei(width), GLsizei(height))
}
/// Set rendering target.
///
/// textureId -- where to draw to
/// x, y, width, height -- area affected by framgent operaions
///
public func setTarget(_ textureId: TextureId?, x: Int = 0, y: Int = 0, width: Int, height: Int) {
setTargetDirty(textureId, x: x, y: y, width: width, height: height)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
}
/// Make an attribute layout.
public func makeAttributeLayout(_ attributes: [Int: AttributeLayoutDescriptor]) -> AttributeLayoutId {
var vertexArrayId = GLuint(0)
glGenVertexArraysOES(1, &vertexArrayId)
glBindVertexArrayOES(vertexArrayId)
for (index, desc) in attributes {
let gltype: GLenum
let attributeSize: GLint
switch desc.type {
case .float32(let size):
attributeSize = GLint(size)
gltype = GLenum(GL_FLOAT)
}
glBindBuffer(GLenum(GL_ARRAY_BUFFER), desc.source)
glVertexAttribPointer(GLuint(index), attributeSize, gltype, GLboolean(GL_FALSE), GLsizei(desc.stride), UnsafeRawPointer(bitPattern: desc.offset))
glVertexAttribDivisorEXT(GLuint(index), GLuint(desc.divisor))
glEnableVertexAttribArray(GLuint(index))
}
glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)
glBindVertexArrayOES(0)
return vertexArrayId
}
/// Order drawn pixels by their depth.
public func enableDepthTest() {
glEnable(GLenum(GL_DEPTH_TEST))
}
/// Stop performing depth test.
public func disableDepthTest() {
glDisable(GLenum(GL_DEPTH_TEST))
}
/// Enable blending.
public func enableBlending() {
glEnable(GLenum(GL_BLEND))
glBlendFunc(GLenum(GL_SRC_ALPHA), GLenum(GL_ONE_MINUS_SRC_ALPHA))
}
/// How to blend.
public func blendFactors(_ factors: BlendFactorSet) {
switch factors {
case .mixBoth:
glBlendFunc(GLenum(GL_SRC_ALPHA), GLenum(GL_ONE_MINUS_SRC_ALPHA))
case .sourceOnly:
glBlendFunc(GLenum(GL_ONE), GLenum(GL_ZERO))
}
}
/// Disable blending.
public func disableBlending() {
glDisable(GLenum(GL_BLEND))
}
/// Fire appropriate `glEnable` calls.
public func enableCapabilities(_ caps: GraphicalCapabilities) {
if caps.contains(.blend) {
enableBlending()
}
if caps.contains(.depthTest) {
enableDepthTest()
}
}
/// Fire appropriate `glDisable` calls.
public func disableCapabilities(_ caps: GraphicalCapabilities) {
if caps.contains(.blend) {
disableBlending()
}
if caps.contains(.depthTest) {
disableDepthTest()
}
}
/// Make a rendering pipeline.
///
/// vertexSource, fragmentSource -- GLSL to feed into the driver
/// attributeOrdering -- assign indices to attributes to refer with in the attribute layout
/// uniformOrdering -- assign indices to uniforms to refer with in render pass 'uniform' calls
/// emitWarningsWith -- if specified, shall be called with each compilation or linkage log regardless of its status
///
public func makePipeline(
vertexSource: String, fragmentSource: String,
attributeOrdering: [String: Int], uniformOrdering: [String: Int],
use capabilities: GraphicalCapabilities = [],
emitWarningsWith warnCallback: ((String) -> ())? = nil
) throws -> Pipeline {
let progId = glCreateProgram()
precondition(progId > 0, "program creation should succeed")
let vertId = glCreateShader(GLenum(GL_VERTEX_SHADER))
defer { glDeleteShader(vertId) }
let fragId = glCreateShader(GLenum(GL_FRAGMENT_SHADER))
defer { glDeleteShader(fragId) }
precondition(vertId > 0 && fragId > 0, "shader creation should succeed")
var status = Int32(0)
vertexSource.withCString {
glShaderSource(vertId, 1, [$0], nil)
}
glCompileShader(vertId)
glGetShaderiv(vertId, GLenum(GL_COMPILE_STATUS), &status)
guard status == GL_TRUE else {
let log = retrieveCompilationLog(id: vertId)
throw GLError.vertexShaderCompilationFailure(log)
}
fragmentSource.withCString {
glShaderSource(fragId, 1, [$0], nil)
}
glCompileShader(fragId)
glGetShaderiv(fragId, GLenum(GL_COMPILE_STATUS), &status)
guard status == GL_TRUE else {
let log = retrieveCompilationLog(id: fragId)
throw GLError.fragmentShaderCompilationFailure(log)
}
glAttachShader(progId, vertId)
glAttachShader(progId, fragId)
defer {
glDetachShader(progId, vertId)
glDetachShader(progId, fragId)
}
// Later we could refer to attributes in the layout using these indices.
for (attrName, attrIndex) in attributeOrdering {
glBindAttribLocation(progId, GLuint(attrIndex), attrName)
}
glLinkProgram(progId)
glGetProgramiv(progId, GLenum(GL_LINK_STATUS), &status)
guard status == GL_TRUE else {
let log = retrieveLinkageLog(id: progId)
throw GLError.programLinkageFailure(log)
}
// Emulate `glBindUniformLocation` as if there was such a thing
// so that we could use arbitrary indices for `uniform` calls in a rendering pass.
var uniformIdMap = Dictionary<Int, GLint>()
for (uniformName, uniformIndex) in uniformOrdering {
let glindex = glGetUniformLocation(progId, uniformName)
uniformIdMap[uniformIndex] = glindex
}
// Log might contain warnings if compilation and linkage status is true.
if let warn = warnCallback {
do {
let log = retrieveCompilationLog(id: vertId)
if log.count > 0 {
warn(log)
}
}
do {
let log = retrieveCompilationLog(id: fragId)
if log.count > 0 {
warn(log)
}
}
do {
let log = retrieveLinkageLog(id: progId)
if log.count > 0 {
warn(log)
}
}
}
return Pipeline(programId: progId, uniformIdMap: uniformIdMap, requiredCapabilities: capabilities)
}
/// We are done for now.
public func pipelinesReady() {
glReleaseShaderCompiler()
}
/// Perform a series of draw calls.
///
/// The state of the GL is always assumed to be in its default values prior to the call to `with`.
/// When `with` finishes, it restored the GL state to its defaults, not to what it actually used to be.
///
public func with(_ pipeline: Pipeline, _ work: (RenderPass) -> ()) {
enableCapabilities(pipeline.requiredCapabilities)
defer {
disableCapabilities(pipeline.requiredCapabilities)
}
glUseProgram(pipeline.programId)
defer {
glUseProgram(0)
}
work(RenderPass(pipeline: pipeline))
}
/// Wrap compilation log in a native String.
private func retrieveCompilationLog(id: GLuint) -> String {
let oughtToBeEnough = 1024
var storage = ContiguousArray<CChar>(repeating: 0, count: oughtToBeEnough)
var log: String! = nil
storage.withUnsafeMutableBufferPointer {
glGetShaderInfoLog(id, GLsizei(oughtToBeEnough), nil, $0.baseAddress!)
log = String(cString: $0.baseAddress!)
}
return log
}
/// Wrap linkage log in a native String.
private func retrieveLinkageLog(id: GLuint) -> String {
let oughtToBeEnough = 1024
var storage = ContiguousArray<CChar>(repeating: 0, count: oughtToBeEnough)
var log: String! = nil
storage.withUnsafeMutableBufferPointer {
glGetProgramInfoLog(id, GLsizei(oughtToBeEnough), nil, $0.baseAddress!)
log = String(cString: $0.baseAddress!)
}
return log
}
}
/// A device default for the system.
public let draw = DrawContext()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment