|
// |
|
// 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() |