unit KM_Render_Mobile; interface uses KM_RenderTypes; procedure KMRenderMobileInvalidateCurrentContext; procedure KMRenderMobileInitializeCurrentContext; function KMRenderMobileTextureBackendReady: Boolean; function KMRenderMobileMaxTextureSize: Cardinal; procedure KMRenderMobileBindTexture(aTexId: Cardinal); function KMRenderMobileCreateTexture(aMinFilter, aMagFilter: TKMFilterType): Cardinal; procedure KMRenderMobileDeleteTexture(aTexId: Cardinal); procedure KMRenderMobileUpdateTexture(aTexture: Cardinal; aWidth, aHeight: Word; aMode: TKMTexFormat; const aData: Pointer); procedure KMRenderMobileInvalidateTextureBindingCache; procedure KMRenderMobileApplyOrdinary2DProjection(aScreenX, aScreenY: Word); procedure KMRenderMobileClearCurrentTarget; procedure KMRenderMobileSetActiveTextureUnit0; procedure KMRenderMobileDrawSampledTriangles( const aPositionData, aTexCoordData: Pointer; aStride, aVertexCount: Integer; aColorR, aColorG, aColorB, aColorA: Single); procedure KMRenderMobileDrawSolidTriangles( const aPositionData: Pointer; aStride, aVertexCount: Integer; aColorR, aColorG, aColorB, aColorA: Single); procedure KMRenderMobileActivateSampledProgram; procedure KMRenderMobileInvalidateSampledProgram; procedure KMRenderMobileDisposeSolidMesh; procedure KMRenderMobileInitializeSampledProgram; function KMRenderMobileSampledProgramReady: Boolean; procedure KMRenderMobileDrawBrightnessOverlay( aIntensity: Single; aWidth, aHeight: Word); implementation uses SysUtils, CastleGLUtils, CastleGLES, CastleRectangles, CastleProjection, CastleRenderContext, CastleVectors, CastleGLShaders, CastleRenderOptions, CastleRenderPrimitives, Math; var gTextureBackendReady: Boolean; gTextureBindingKnown: Boolean; gCachedTextureId: Cardinal; gSampledProgram: TGLSLProgram; gSolidMesh: TCastleRenderUnlitMesh = nil; const SampledVertexShader = 'attribute vec3 aPosition;' + #10 + 'attribute vec2 aTexCoord;' + #10 + 'uniform mat4 uProjection;' + #10 + 'varying vec2 vTexCoord;' + #10 + 'void main() {' + #10 + ' gl_Position = uProjection * vec4(aPosition, 1.0);' + #10 + ' vTexCoord = aTexCoord;' + #10 + '}'; SampledFragmentShader = 'uniform sampler2D uTexture;' + #10 + 'uniform vec4 uColor;' + #10 + 'varying vec2 vTexCoord;' + #10 + 'void main() {' + #10 + ' gl_FragColor = texture2D(uTexture, vTexCoord) * uColor;' + #10 + '}'; procedure KMRenderMobileApplyOrdinary2DProjection(aScreenX, aScreenY: Word); var Dimensions: TFloatRectangle; begin Dimensions.Left := 0; Dimensions.Width := aScreenX; Dimensions.Bottom := aScreenY; Dimensions.Height := -Single(aScreenY); RenderContext.ProjectionMatrix := OrthoProjectionMatrix(Dimensions, -100, 100); end; procedure KMRenderMobileClearCurrentTarget; begin RenderContext.Clear([cbColor, cbDepth, cbStencil], TVector4.Zero); end; procedure KMRenderMobileDrawBrightnessOverlay( aIntensity: Single; aWidth, aHeight: Word); begin DrawRectangle( Rectangle(0, 0, aWidth, aHeight), Vector4(aIntensity, aIntensity, aIntensity, aIntensity), bsDstColor, bdOne, True); RenderContext.BlendingEnable(bsSrcAlpha, bdOneMinusSrcAlpha); end; procedure KMRenderMobileSetActiveTextureUnit0; begin CastleGLES.glActiveTexture(CastleGLES.GL_TEXTURE0); end; procedure KMRenderMobileInitializeSampledProgram; var candidate: TGLSLProgram; positionAttribute, texCoordAttribute: TGLSLAttribute; projectionUniform, textureUniform, colorUniform: TGLSLUniform; begin if gSampledProgram <> nil then Exit; candidate := TGLSLProgram.Create; try candidate.FragmentShaderRequired := True; candidate.AttachVertexShader(SampledVertexShader); candidate.AttachFragmentShader(SampledFragmentShader); candidate.Link; positionAttribute := candidate.Attribute('aPosition'); texCoordAttribute := candidate.Attribute('aTexCoord'); projectionUniform := candidate.Uniform('uProjection', umIgnore); textureUniform := candidate.Uniform('uTexture', umIgnore); colorUniform := candidate.Uniform('uColor', umIgnore); if (projectionUniform.Location = -1) or (textureUniform.Location = -1) or (colorUniform.Location = -1) then raise Exception.Create('Sampled shader is missing a required uniform'); gSampledProgram := candidate; candidate := nil; finally candidate.Free; end; end; procedure KMRenderMobileActivateSampledProgram; begin if gSampledProgram = nil then raise Exception.Create('Sampled shader program is not ready'); gSampledProgram.Enable; end; procedure KMRenderMobileInvalidateSampledProgram; begin FreeAndNil(gSampledProgram); end; procedure KMRenderMobileDisposeSolidMesh; begin FreeAndNil(gSolidMesh); end; function KMRenderMobileSampledProgramReady: Boolean; begin Result := gSampledProgram <> nil; end; procedure KMRenderMobileDrawSampledTriangles( const aPositionData, aTexCoordData: Pointer; aStride, aVertexCount: Integer; aColorR, aColorG, aColorB, aColorA: Single); var positionAttribute, texCoordAttribute: TGLSLAttribute; projectionUniform, colorUniform: TGLSLUniform; positionEnabled, texCoordEnabled: Boolean; begin if (aPositionData = nil) or (aTexCoordData = nil) or (aStride <= 0) or (aVertexCount <= 0) then raise Exception.Create('Invalid sampled triangle draw input'); if gSampledProgram = nil then KMRenderMobileActivateSampledProgram; positionAttribute := gSampledProgram.Attribute('aPosition'); texCoordAttribute := gSampledProgram.Attribute('aTexCoord'); projectionUniform := gSampledProgram.Uniform('uProjection', umIgnore); colorUniform := gSampledProgram.Uniform('uColor', umIgnore); if (projectionUniform.Location = -1) or (colorUniform.Location = -1) then raise Exception.Create('Sampled shader is missing a required draw uniform'); KMRenderMobileActivateSampledProgram; projectionUniform.SetValue(RenderContext.ProjectionMatrix); colorUniform.SetValue(Vector4(aColorR, aColorG, aColorB, aColorA)); RenderContext.BindBuffer[btArray] := 0; positionEnabled := False; texCoordEnabled := False; try positionAttribute.EnableArrayVector3(nil, CastleGLES.GLsizei(aStride), PtrUInt(aPositionData)); positionEnabled := True; texCoordAttribute.EnableArrayVector2(nil, CastleGLES.GLsizei(aStride), PtrUInt(aTexCoordData)); texCoordEnabled := True; CastleGLES.glDrawArrays(CastleGLES.GL_TRIANGLES, 0, CastleGLES.GLsizei(aVertexCount)); finally if texCoordEnabled then texCoordAttribute.DisableArray; if positionEnabled then positionAttribute.DisableArray; end; end; procedure KMRenderMobileDrawSolidTriangles( const aPositionData: Pointer; aStride, aVertexCount: Integer; aColorR, aColorG, aColorB, aColorA: Single); var i: Integer; vertexData: NativeUInt; x, y, z: Single; vertices: array of TVector4; drawException: TObject; begin if (aVertexCount <= 0) or (aPositionData = nil) or (aStride < 3 * SizeOf(Single)) or ((aVertexCount mod 3) <> 0) then raise Exception.Create('Invalid solid triangle draw input'); for i := 0 to aVertexCount - 1 do begin vertexData := NativeUInt(aPositionData) + NativeUInt(i) * NativeUInt(aStride); Move(Pointer(vertexData)^, x, SizeOf(x)); Move(Pointer(vertexData + SizeOf(Single))^, y, SizeOf(y)); Move(Pointer(vertexData + 2 * SizeOf(Single))^, z, SizeOf(z)); if IsNan(x) or IsInfinite(x) or IsNan(y) or IsInfinite(y) or IsNan(z) or IsInfinite(z) then raise Exception.Create('Solid triangle coordinates must be finite'); end; if IsNan(aColorR) or IsInfinite(aColorR) or IsNan(aColorG) or IsInfinite(aColorG) or IsNan(aColorB) or IsInfinite(aColorB) or IsNan(aColorA) or IsInfinite(aColorA) then raise Exception.Create('Solid triangle color must be finite'); if not gTextureBackendReady then raise Exception.Create('current graphics backend unavailable'); SetLength(vertices, aVertexCount); for i := 0 to aVertexCount - 1 do begin vertexData := NativeUInt(aPositionData) + NativeUInt(i) * NativeUInt(aStride); Move(Pointer(vertexData)^, x, SizeOf(x)); Move(Pointer(vertexData + SizeOf(Single))^, y, SizeOf(y)); Move(Pointer(vertexData + 2 * SizeOf(Single))^, z, SizeOf(z)); vertices[i] := Vector4(x, y, z, 1.0); end; if gSolidMesh = nil then gSolidMesh := TCastleRenderUnlitMesh.Create(True); drawException := nil; try try gSolidMesh.SetVertexes(vertices, True); gSolidMesh.Color := Vector4(aColorR, aColorG, aColorB, aColorA); gSolidMesh.ModelViewProjection := RenderContext.ProjectionMatrix; gSolidMesh.Render(pmTriangles); except drawException := ExceptObject; raise; end; finally try try RenderContext.CurrentVao := nil; finally try RenderContext.BindBuffer[btArray] := 0; finally RenderContext.BindBuffer[btElementArray] := 0; end; end; except if drawException = nil then raise; // Keep the active draw exception even if cleanup or diagnostic allocation fails. try if (drawException is Exception) and (ExceptObject is Exception) then Exception(drawException).Message := Exception(drawException).Message + '; solid binding reset failed: ' + Exception(ExceptObject).Message; except end; end; end; end; procedure RequireTextureBackendReady; begin if not gTextureBackendReady then raise Exception.Create('Texture backend is not ready'); end; procedure KMRenderMobileInvalidateCurrentContext; begin gTextureBackendReady := False; KMRenderMobileInvalidateTextureBindingCache; end; procedure KMRenderMobileInitializeCurrentContext; begin gTextureBackendReady := False; try KMRenderMobileInvalidateTextureBindingCache; if (GLFeatures = nil) or (GLFeatures.MaxTextureSize = 0) then raise Exception.Create('Texture backend initialization failed'); if not (Assigned(glGenTextures) and Assigned(glBindTexture) and Assigned(glDeleteTextures) and Assigned(glTexParameteri) and Assigned(glTexImage2D) and Assigned(glGetIntegerv) and Assigned(glPixelStorei) and Assigned(CastleGLES.glGetError)) then raise Exception.Create('Texture backend initialization failed'); gTextureBackendReady := True; except gTextureBackendReady := False; raise; end; end; function DrainTextureGLErrors: string; var ErrorCode: CastleGLES.GLenum; begin Result := ''; repeat ErrorCode := CastleGLES.glGetError(); if ErrorCode <> CastleGLES.GL_NO_ERROR then begin if Result <> '' then Result := Result + ', '; Result := Result + '0x' + IntToHex(ErrorCode, 8); end; until ErrorCode = CastleGLES.GL_NO_ERROR; end; procedure RequireTextureNoGLError(const aOperation, aStage: string); var Errors: string; begin Errors := DrainTextureGLErrors; if Errors <> '' then begin KMRenderMobileInvalidateTextureBindingCache; raise Exception.Create(Format('texture %s — current GLES error at %s: %s', [aOperation, aStage, Errors])); end; end; procedure RequireNoPreexistingTextureGLError(const aOperation: string); var Errors: string; begin Errors := DrainTextureGLErrors; if Errors <> '' then begin KMRenderMobileInvalidateTextureBindingCache; raise Exception.Create(Format('texture %s — pre-existing GLES error(s): %s', [aOperation, Errors])); end; end; procedure KMRenderMobileBindTexture(aTexId: Cardinal); begin RequireTextureBackendReady; if gTextureBindingKnown and (gCachedTextureId = aTexId) then Exit; CastleGLES.glBindTexture(CastleGLES.GL_TEXTURE_2D, CastleGLES.GLuint(aTexId)); gCachedTextureId := aTexId; gTextureBindingKnown := True; end; function IsPowerOfTwo(aValue: Cardinal): Boolean; begin Result := (aValue <> 0) and ((aValue and (aValue - 1)) = 0); end; procedure KMRenderMobileUpdateTexture(aTexture: Cardinal; aWidth, aHeight: Word; aMode: TKMTexFormat; const aData: Pointer); var alphaData: array of Byte; pixelCount, I: Cardinal; unpackAlignment: CastleGLES.GLint; alignmentKnown, alignmentSetAttempted, bindAttempted: Boolean; operationUnverified: Boolean; finalizeErrors, finalizeEntryErrors: string; procedure AddFinalizeError(const aStage, aMessage: string); begin if finalizeErrors <> '' then finalizeErrors := finalizeErrors + '; '; finalizeErrors := finalizeErrors + aStage + ': ' + aMessage; end; procedure CollectFinalizePending(const aStage: string); var Errors: string; begin if not gTextureBackendReady then Exit; try Errors := DrainTextureGLErrors; if Errors <> '' then AddFinalizeError(aStage, Errors); except on E: Exception do AddFinalizeError(aStage, E.Message); end; end; procedure FinalizeUpdate(aCheckPending: Boolean); begin finalizeEntryErrors := ''; if aCheckPending and operationUnverified and gTextureBackendReady then try finalizeEntryErrors := DrainTextureGLErrors; if finalizeEntryErrors <> '' then AddFinalizeError('update/finalize-entry', finalizeEntryErrors); except on E: Exception do AddFinalizeError('update/finalize-entry', E.Message); end; if bindAttempted then if gTextureBackendReady then try operationUnverified := True; KMRenderMobileBindTexture(0); RequireTextureNoGLError('update', 'unbind'); operationUnverified := False; except on E: Exception do begin operationUnverified := False; AddFinalizeError('update/unbind', E.Message); CollectFinalizePending('update/unbind'); end; end else AddFinalizeError('update/unbind', 'current texture context is unavailable'); if alignmentKnown and alignmentSetAttempted and (unpackAlignment <> 1) then if gTextureBackendReady then try operationUnverified := True; CastleGLES.glPixelStorei(CastleGLES.GL_UNPACK_ALIGNMENT, unpackAlignment); RequireTextureNoGLError('update', 'restore-unpack'); operationUnverified := False; except on E: Exception do begin operationUnverified := False; AddFinalizeError('update/restore-unpack', E.Message); CollectFinalizePending('update/restore-unpack'); end; end else AddFinalizeError('update/restore-unpack', 'current texture context is unavailable'); end; begin try RequireTextureBackendReady; if (aTexture = 0) or (aWidth = 0) or (aHeight = 0) or not IsPowerOfTwo(aWidth) or not IsPowerOfTwo(aHeight) or (aData = nil) then raise Exception.Create('Invalid texture upload parameters'); if not (aMode in [tfRGB5A1, tfRGBA8, tfAlpha8]) then raise Exception.Create('Unsupported texture upload format'); RequireNoPreexistingTextureGLError('update'); if aMode = tfAlpha8 then begin pixelCount := Cardinal(aWidth) * Cardinal(aHeight); SetLength(alphaData, pixelCount); for I := 0 to pixelCount - 1 do alphaData[I] := Byte((PCardinal(aData)[I] shr 24) and $FF); end; alignmentKnown := False; alignmentSetAttempted := False; bindAttempted := False; operationUnverified := False; finalizeErrors := ''; try operationUnverified := True; CastleGLES.glGetIntegerv(CastleGLES.GL_UNPACK_ALIGNMENT, @unpackAlignment); RequireTextureNoGLError('update', 'glGetIntegerv'); operationUnverified := False; alignmentKnown := True; if unpackAlignment <> 1 then begin alignmentSetAttempted := True; operationUnverified := True; CastleGLES.glPixelStorei(CastleGLES.GL_UNPACK_ALIGNMENT, 1); RequireTextureNoGLError('update', 'glPixelStorei(1)'); operationUnverified := False; end; bindAttempted := True; operationUnverified := True; KMRenderMobileBindTexture(aTexture); RequireTextureNoGLError('update', 'glBindTexture'); operationUnverified := False; case aMode of tfRGB5A1, tfRGBA8: begin operationUnverified := True; CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_R, CastleGLES.GL_RED); RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_R)'); operationUnverified := False; operationUnverified := True; CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_G, CastleGLES.GL_GREEN); RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_G)'); operationUnverified := False; operationUnverified := True; CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_B, CastleGLES.GL_BLUE); RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_B)'); operationUnverified := False; operationUnverified := True; CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_A, CastleGLES.GL_ALPHA); RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_A)'); operationUnverified := False; if aMode = tfRGB5A1 then begin operationUnverified := True; CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_RGB5_A1, aWidth, aHeight, 0, CastleGLES.GL_RGBA, CastleGLES.GL_UNSIGNED_BYTE, aData); RequireTextureNoGLError('update', 'glTexImage2D'); operationUnverified := False; end else begin operationUnverified := True; CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_RGBA8, aWidth, aHeight, 0, CastleGLES.GL_RGBA, CastleGLES.GL_UNSIGNED_BYTE, aData); RequireTextureNoGLError('update', 'glTexImage2D'); operationUnverified := False; end; end; tfAlpha8: begin operationUnverified := True; CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_R, CastleGLES.GL_ONE); RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_R)'); operationUnverified := False; operationUnverified := True; CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_G, CastleGLES.GL_ONE); RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_G)'); operationUnverified := False; operationUnverified := True; CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_B, CastleGLES.GL_ONE); RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_B)'); operationUnverified := False; operationUnverified := True; CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_SWIZZLE_A, CastleGLES.GL_RED); RequireTextureNoGLError('update', 'glTexParameteri(GL_TEXTURE_SWIZZLE_A)'); operationUnverified := False; operationUnverified := True; CastleGLES.glTexImage2D(CastleGLES.GL_TEXTURE_2D, 0, CastleGLES.GL_R8, aWidth, aHeight, 0, CastleGLES.GL_RED, CastleGLES.GL_UNSIGNED_BYTE, @alphaData[0]); RequireTextureNoGLError('update', 'glTexImage2D'); operationUnverified := False; end; end; except on E: Exception do begin KMRenderMobileInvalidateTextureBindingCache; FinalizeUpdate(True); if finalizeErrors <> '' then E.Message := E.Message + '; secondary finalize failure(s): ' + finalizeErrors; raise; end; end; FinalizeUpdate(False); if finalizeErrors <> '' then raise Exception.Create('texture update failed: ' + finalizeErrors); except KMRenderMobileInvalidateTextureBindingCache; raise; end; end; function TextureFilter(aFilter: TKMFilterType): CastleGLES.GLint; begin case aFilter of ftNearest: Result := CastleGLES.GL_NEAREST; ftLinear: Result := CastleGLES.GL_LINEAR; end; end; function KMRenderMobileCreateTexture(aMinFilter, aMagFilter: TKMFilterType): Cardinal; var texture: CastleGLES.GLuint; cleanupErrors, cleanupEntryErrors, cleanupFailure: string; cleanupReady, cleanupFailed: Boolean; begin RequireTextureBackendReady; RequireNoPreexistingTextureGLError('create'); texture := 0; try CastleGLES.glGenTextures(1, @texture); RequireTextureNoGLError('create', 'glGenTextures'); if texture = 0 then raise Exception.Create('Texture generation failed'); KMRenderMobileBindTexture(Cardinal(texture)); RequireTextureNoGLError('create', 'glBindTexture'); CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MIN_FILTER, TextureFilter(aMinFilter)); RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_MIN_FILTER)'); CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MAG_FILTER, TextureFilter(aMagFilter)); RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_MAG_FILTER)'); CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_WRAP_S, CastleGLES.GL_CLAMP_TO_EDGE); RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_WRAP_S)'); CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_WRAP_T, CastleGLES.GL_CLAMP_TO_EDGE); RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_WRAP_T)'); CastleGLES.glTexParameteri(CastleGLES.GL_TEXTURE_2D, CastleGLES.GL_TEXTURE_MAX_LEVEL, 0); RequireTextureNoGLError('create', 'glTexParameteri(GL_TEXTURE_MAX_LEVEL)'); Result := Cardinal(texture); except on E: Exception do begin KMRenderMobileInvalidateTextureBindingCache; try cleanupErrors := ''; cleanupEntryErrors := ''; cleanupFailure := ''; cleanupFailed := False; if texture <> 0 then begin cleanupReady := False; try RequireTextureBackendReady; cleanupReady := True; except on CleanupE: Exception do begin cleanupFailed := True; cleanupFailure := 'cleanup unavailable: ' + CleanupE.Message; end; end; if cleanupReady then begin try cleanupEntryErrors := DrainTextureGLErrors; except on CleanupE: Exception do begin cleanupFailed := True; cleanupFailure := 'cleanup-entry drain failed: ' + CleanupE.Message; end; end; try KMRenderMobileDeleteTexture(Cardinal(texture)); except on CleanupE: Exception do begin cleanupFailed := True; if cleanupFailure <> '' then cleanupFailure := cleanupFailure + '; '; cleanupFailure := cleanupFailure + 'glDeleteTextures failed: ' + CleanupE.Message; end; end; try cleanupErrors := DrainTextureGLErrors; if cleanupErrors <> '' then begin cleanupFailed := True; if cleanupFailure <> '' then cleanupFailure := cleanupFailure + '; '; cleanupFailure := cleanupFailure + 'glDeleteTextures GLES error(s): ' + cleanupErrors; end; except on CleanupE: Exception do begin cleanupFailed := True; if cleanupFailure <> '' then cleanupFailure := cleanupFailure + '; '; cleanupFailure := cleanupFailure + 'cleanup drain failed: ' + CleanupE.Message; end; end; if cleanupEntryErrors <> '' then begin if cleanupFailed then begin if cleanupFailure <> '' then cleanupFailure := cleanupFailure + '; '; cleanupFailure := cleanupFailure + 'cleanup-entry GLES error(s): ' + cleanupEntryErrors; end; end; end; end; KMRenderMobileInvalidateTextureBindingCache; if cleanupFailed then try E.Message := E.Message + '; texture create cleanup failed: ' + cleanupFailure; except end; except KMRenderMobileInvalidateTextureBindingCache; end; raise; end; end; end; procedure KMRenderMobileDeleteTexture(aTexId: Cardinal); var texture: CastleGLES.GLuint; begin RequireTextureBackendReady; if aTexId = 0 then Exit; texture := CastleGLES.GLuint(aTexId); CastleGLES.glDeleteTextures(1, @texture); if gTextureBindingKnown and (gCachedTextureId = aTexId) then gCachedTextureId := 0; end; procedure KMRenderMobileInvalidateTextureBindingCache; begin gTextureBindingKnown := False; end; function KMRenderMobileTextureBackendReady: Boolean; begin Result := gTextureBackendReady; end; function KMRenderMobileMaxTextureSize: Cardinal; begin RequireTextureBackendReady; if GLFeatures = nil then raise Exception.Create('Texture backend feature state is unavailable'); Result := GLFeatures.MaxTextureSize; if Result = 0 then raise Exception.Create('Texture backend maximum texture size is unavailable'); end; end.