diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index be8dabaa..f0855921 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,7 +8,25 @@ ### 5.4.10-prerelease0001 - [Text] improved Font resolver for Windows and MacOS -- [PathSegment] fixed several PathSegment tools and added a few new ones +- [PathSegment] fixed several PathSegment tools and added a few new ones### 5.4.7 +- Fixed Frustum.withAspect and Frustum.withHorizontalFieldOfViewInDegrees +- [GL] Fixed InvalidEnum error due to GL_POINT_SPRITE +- [GL] Removed validation via proxy textures (resulted in errors on AMD with multisampled textures) +- [GL] Removed swizzle for multisampled textures (not supported) +- [GL] Added simple parameter device limit checks for textures and renderbuffers +- [GL] Improved texture memory usage tracking +- [GL] Made retrieval of program binaries more robust +- [GL] Improved driver information and error formatting +- [GL] Disabled Dispose() for Program +- [GL] Fixed resource leaks in ContextHandleOpenTK.create +- [GL] Fixed ComputeCommand.SetBufferCmd +- [GL] Fixed issue with texture targets and multisampling +- [Vulkan] Fixed swapchain creation if maxImages is zero +- [Vulkan] Fixed issue with image format queries and external memory +- [Vulkan] Improved error formatting +- [GLFW] Use no error context only when indicated by debug config +- Added IRenderTask.GetRuntime() and IRenderTask.GetFramebufferSignature() + ### 5.4.6 - [ContextHandles] GL.Enable(EnableCap.PointSprite) diff --git a/paket.dependencies b/paket.dependencies index 12763937..0d2c7ecd 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -1,12 +1,11 @@ framework: auto-detect source https://api.nuget.org/v3/index.json -source https://vrvis.myget.org/F/aardvark_public/api/v2 storage: none nuget FSharp.Core >= 5.0.1 lowest_matching: true nuget Microsoft.NETFramework.ReferenceAssemblies >= 1.0.0 lowest_matching: true -nuget Aardvark.Build ~> 1.0.18 +nuget Aardvark.Build ~> 1.0.21 nuget FSharp.Data.Adaptive ~> 1.2.13 nuget CSharp.Data.Adaptive ~> 1.2.13 @@ -23,9 +22,9 @@ nuget Aardvark.Base.Tensors ~> 5.2.27 nuget Aardvark.Assembler ~> 0.0.8 -nuget FShade.Core ~> 5.5.0 -nuget FShade ~> 5.5.0 -nuget FShade.Debug ~> 5.5.0 +nuget FShade.Core ~> 5.5.3 +nuget FShade ~> 5.5.3 +nuget FShade.Debug ~> 5.5.3 nuget Unofficial.OpenVR ~> 1.1.0 nuget Unofficial.Typography ~> 0.1.0 diff --git a/paket.lock b/paket.lock index fc6fdd36..3e9e9a5d 100644 --- a/paket.lock +++ b/paket.lock @@ -10,53 +10,53 @@ NUGET Aardvark.Base.Runtime (>= 5.2.7 < 5.3) FSharp.Core (>= 5.0) FSharp.Data.Adaptive (>= 1.2.13 < 1.3) - Aardvark.Base (5.2.27) - Aardvark.Base.Telemetry (5.2.27) + Aardvark.Base (5.2.28) + Aardvark.Base.Telemetry (5.2.28) System.Collections.Immutable (>= 5.0) System.Reflection.Metadata (>= 5.0) - restriction: || (== net471) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net6.0-windows7.0) (< netcoreapp3.1)) (== netstandard2.0) System.Text.Json (>= 4.7.2) - Aardvark.Base.Essentials (5.2.27) - Aardvark.Base (5.2.27) + Aardvark.Base.Essentials (5.2.28) + Aardvark.Base (5.2.28) System.Collections.Immutable (>= 5.0) - Aardvark.Base.FSharp (5.2.27) - Aardvark.Base (5.2.27) + Aardvark.Base.FSharp (5.2.28) + Aardvark.Base (5.2.28) Aardvark.Base.TypeProviders (>= 4.5.15 < 4.6) FSharp.Core (>= 5.0) FSharp.Data.Adaptive (>= 1.2 < 1.3) FsPickler (>= 5.3.2 < 5.4) System.Dynamic.Runtime (>= 4.3 < 4.4) - Aardvark.Base.Incremental (5.2.27) - Aardvark.Base (5.2.27) - Aardvark.Base.FSharp (5.2.27) + Aardvark.Base.Incremental (5.2.28) + Aardvark.Base (5.2.28) + Aardvark.Base.FSharp (5.2.28) Aardvark.Base.TypeProviders (>= 4.5.15 < 4.6) FSharp.Core (>= 5.0) FSharp.Data.Adaptive (>= 1.2 < 1.3) FsPickler (>= 5.3.2 < 5.4) - Aardvark.Base.IO (5.2.27) - Aardvark.Base (5.2.27) - Aardvark.Base.Tensors (5.2.27) + Aardvark.Base.IO (5.2.28) + Aardvark.Base (5.2.28) + Aardvark.Base.Tensors (5.2.28) System.Dynamic.Runtime (>= 4.3 < 4.4) - Aardvark.Base.Runtime (5.2.27) - Aardvark.Base.FSharp (5.2.27) - Aardvark.Base.Incremental (5.2.27) + Aardvark.Base.Runtime (5.2.28) + Aardvark.Base.FSharp (5.2.28) + Aardvark.Base.Incremental (5.2.28) Aardvark.Base.TypeProviders (>= 4.5.15 < 4.6) FSharp.Core (>= 5.0) FSharp.Data.Adaptive (>= 1.2 < 1.3) FsPickler (>= 5.3.2 < 5.4) - Aardvark.Base.Telemetry (5.2.27) - Aardvark.Base.Tensors (5.2.27) - Aardvark.Base (5.2.27) - Aardvark.Base.FSharp (5.2.27) + Aardvark.Base.Telemetry (5.2.28) + Aardvark.Base.Tensors (5.2.28) + Aardvark.Base (5.2.28) + Aardvark.Base.FSharp (5.2.28) FSharp.Core (>= 5.0) SixLabors.ImageSharp (>= 2.1.3 < 2.2) Aardvark.Base.TypeProviders (4.5.15) FSharp.Core (>= 3.1.2.5) - restriction: || (== net471) (&& (== net6.0) (>= net45)) (&& (== net6.0-windows7.0) (>= net45)) (&& (== netstandard2.0) (>= net45)) FSharp.Core (>= 4.2.3) - restriction: || (&& (== net471) (< net45)) (== net6.0) (== net6.0-windows7.0) (== netstandard2.0) - Aardvark.Build (1.0.19) - Aardvark.Geometry (5.2.27) - Aardvark.Base (5.2.27) - Aardvark.Base.FSharp (5.2.27) - Aardvark.Base.Tensors (5.2.27) + Aardvark.Build (1.0.21) + Aardvark.Geometry (5.2.28) + Aardvark.Base (5.2.28) + Aardvark.Base.FSharp (5.2.28) + Aardvark.Base.Tensors (5.2.28) Aardvark.Base.TypeProviders (>= 4.5.15 < 4.6) FSharp.Core (>= 5.0) FSharp.Data.Adaptive (>= 1.2 < 1.3) @@ -69,38 +69,38 @@ NUGET FSharp.Core (>= 4.7) FSharp.Data.Adaptive (1.2.14) System.Reflection.Emit.Lightweight (>= 4.6) - FShade (5.5) - FShade.Core (5.5) - FShade.GLSL (5.5) - FShade.Imperative (5.5) - FShade.SpirV (5.5) - FShade.Core (5.5) + FShade (5.5.3) + FShade.Core (5.5.3) + FShade.GLSL (5.5.3) + FShade.Imperative (5.5.3) + FShade.SpirV (5.5.3) + FShade.Core (5.5.3) Aardvark.Base (>= 5.2.19 < 5.3) Aardvark.Base.FSharp (>= 5.2.19 < 5.3) - FShade.Imperative (5.5) + FShade.Imperative (5.5.3) FSharp.Core (>= 5.0) - FShade.Debug (5.5) + FShade.Debug (5.5.3) Aardvark.Base (>= 5.2.19 < 5.3) Aardvark.Base.FSharp (>= 5.2.19 < 5.3) - FShade.Core (5.5) - FShade.Imperative (5.5) + FShade.Core (5.5.3) + FShade.Imperative (5.5.3) FSharp.Core (>= 5.0) - FShade.GLSL (5.5) + FShade.GLSL (5.5.3) Aardvark.Base (>= 5.2.19 < 5.3) Aardvark.Base.FSharp (>= 5.2.19 < 5.3) - FShade.Core (5.5) - FShade.Imperative (5.5) + FShade.Core (5.5.3) + FShade.Imperative (5.5.3) FSharp.Core (>= 5.0) - FShade.Imperative (5.5) + FShade.Imperative (5.5.3) Aardvark.Base (>= 5.2.19 < 5.3) Aardvark.Base.FSharp (>= 5.2.19 < 5.3) FSharp.Core (>= 5.0) FsPickler (>= 5.3.2 < 5.4) - FShade.SpirV (5.5) + FShade.SpirV (5.5.3) Aardvark.Base (>= 5.2.19 < 5.3) Aardvark.Base.FSharp (>= 5.2.19 < 5.3) - FShade.Core (5.5) - FShade.Imperative (5.5) + FShade.Core (5.5.3) + FShade.Imperative (5.5.3) FSharp.Core (>= 5.0) FSharp.Core (5.0.1) FSharp.Data (4.2.10) diff --git a/src/Aardvark.Rendering.GL/Aardvark.Rendering.GL.fsproj b/src/Aardvark.Rendering.GL/Aardvark.Rendering.GL.fsproj index 64673e94..daa476f6 100644 --- a/src/Aardvark.Rendering.GL/Aardvark.Rendering.GL.fsproj +++ b/src/Aardvark.Rendering.GL/Aardvark.Rendering.GL.fsproj @@ -56,8 +56,9 @@ - + + diff --git a/src/Aardvark.Rendering.GL/Core/Context.fs b/src/Aardvark.Rendering.GL/Core/Context.fs index df67edf2..719a2d1e 100644 --- a/src/Aardvark.Rendering.GL/Core/Context.fs +++ b/src/Aardvark.Rendering.GL/Core/Context.fs @@ -201,10 +201,22 @@ type Context(runtime : IRuntime, createContext : unit -> ContextHandle) as this let mutable unpackAlignment : Option = None + let mutable maxTextureSize : Option = None + + let mutable maxTextureSize3d : Option = None + + let mutable maxTextureSizeCube : Option = None + + let mutable maxTextureArrayLayers : Option = None + + let mutable maxRenderbufferSize : Option = None + let mutable maxComputeWorkGroupSize : Option = None let mutable maxComputeWorkGroupInvocations : Option = None + let mutable numProgramBinaryFormats : Option = None + let mutable shaderCachePath : Option = Some defaultShaderCachePath let formatSampleCounts = FastConcurrentDict() @@ -259,6 +271,34 @@ type Context(runtime : IRuntime, createContext : unit -> ContextHandle) as this GL.GetInteger(GetPName.UnpackAlignment) ) + member x.MaxTextureSize = + getOrQuery &maxTextureSize (fun _ -> + let s = GL.GetInteger(GetPName.MaxTextureSize) + V2i s + ) + + member x.MaxTextureSize3D = + getOrQuery &maxTextureSize3d (fun _ -> + let s = GL.GetInteger(GetPName.Max3DTextureSize) + V3i s + ) + + member x.MaxTextureSizeCube = + getOrQuery &maxTextureSizeCube (fun _ -> + GL.GetInteger(GetPName.MaxCubeMapTextureSize) + ) + + member x.MaxTextureArrayLayers = + getOrQuery &maxTextureArrayLayers (fun _ -> + GL.GetInteger(GetPName.MaxArrayTextureLayers) + ) + + member x.MaxRenderbufferSize = + getOrQuery &maxRenderbufferSize (fun _ -> + let s = GL.GetInteger(GetPName.MaxRenderbufferSize) + V2i s + ) + member x.MaxComputeWorkGroupSize = getOrQuery &maxComputeWorkGroupSize (fun _ -> let mutable res = V3i.Zero @@ -273,6 +313,11 @@ type Context(runtime : IRuntime, createContext : unit -> ContextHandle) as this GL.GetInteger(GetPName.MaxComputeWorkGroupInvocations) ) + member x.NumProgramBinaryFormats = + getOrQuery &numProgramBinaryFormats (fun _ -> + GL.GetInteger(GetPName.NumProgramBinaryFormats) + ) + member internal x.ImportMemoryBlock(external : ExternalMemoryBlock) = sharedMemoryManager.Import external diff --git a/src/Aardvark.Rendering.GL/Core/ContextHandles.fs b/src/Aardvark.Rendering.GL/Core/ContextHandles.fs index 467c0f6f..9437f7d0 100644 --- a/src/Aardvark.Rendering.GL/Core/ContextHandles.fs +++ b/src/Aardvark.Rendering.GL/Core/ContextHandles.fs @@ -31,12 +31,23 @@ module ContextHandleGLExtensions = type GL with static member SetDefaultStates() = GL.Enable(EnableCap.TextureCubeMapSeamless) + GL.Check "cannot enable GL_TEXTURE_CUBE_MAP_SEAMLESS" + + // Note: This is supposed to be deprecated since OpenGL 3.2 and enabled by default. + // However, for some AMD drivers you still need to enable it even though it should not exist anymore. GL.Enable(EnableCap.PointSprite) + GL.GetError() |> ignore + GL.Disable(EnableCap.PolygonSmooth) + GL.Check "cannot disable GL_POLYGON_SMOOTH" + GL.Hint(HintTarget.FragmentShaderDerivativeHint, HintMode.Nicest) + GL.Check "cannot set GL_FRAGMENT_SHADER_DERIVATIVE_HINT to GL_NICEST" + if RuntimeConfig.DepthRange = DepthRange.ZeroToOne then if GL.ARB_clip_control then GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.ZeroToOne) + GL.Check "failed to set depth range to [0, 1]" else failf "cannot set depth range to [0, 1] without GL_ARB_clip_control or OpenGL 4.5" @@ -50,6 +61,7 @@ type ContextHandle(handle : IGraphicsContext, window : IWindowInfo) = static let contextError = new Event() let l = obj() + let onDisposed = Event() let mutable debugOutput = None let mutable onMakeCurrent : ConcurrentHashSet unit> = null let mutable driverInfo = None @@ -66,6 +78,9 @@ type ContextHandle(handle : IGraphicsContext, window : IWindowInfo) = [] static member ContextError = contextError.Publish + [] + member x.OnDisposed = onDisposed.Publish + member x.GetProcAddress(name : string) = (handle |> unbox).GetAddress(name) @@ -187,6 +202,7 @@ type ContextHandle(handle : IGraphicsContext, window : IWindowInfo) = member x.Dispose() = debugOutput |> Option.iter (fun dbg -> dbg.Dispose()) + onDisposed.Trigger() interface IDisposable with member x.Dispose() = x.Dispose() @@ -204,9 +220,6 @@ module ContextHandle = if ctx.IsCurrent then ctx.ReleaseCurrent() ctx.Dispose() - //match windows.TryRemove ctx with - // | (true, w) -> w.Dispose() - // | _ -> () /// /// checks whether the given context is current on the calling thread @@ -225,8 +238,45 @@ module ContextHandle = let releaseCurrent (ctx : ContextHandle) = ctx.ReleaseCurrent() module ContextHandleOpenTK = - - let private windows = System.Collections.Concurrent.ConcurrentDictionary() + + // This is a workaround for closing the X11 display, since OpenTK leaks the display even when the + // window is disposed. This leads to problems when running multiple unit tests one after another. + // We currently use a custom version of OpenTK in which this issue isn't fixed yet. + // https://github.com/krauthaufen/OpenTKHack + // See: https://github.com/opentk/opentk/pull/773 + module private X11 = + open OpenTK.Platform.X11 + open System.Reflection + + [] + module private Internals = + let asm = typeof.Assembly + + let fiImplementation = + typeof.GetField("implementation", BindingFlags.NonPublic ||| BindingFlags.Instance) + + let fiWindow = + let t = asm.GetType("OpenTK.Platform.X11.X11GLNative") + if isNull t then null + else t.GetField("window", BindingFlags.NonPublic ||| BindingFlags.Instance) + + let fCloseDisplay = + let t = asm.GetType("OpenTK.Platform.X11.Functions") + if isNull t then null + else t.GetMethod("XCloseDisplay") + + let closeDisplay (window : NativeWindow) = + if RuntimeInformation.IsOSPlatform OSPlatform.Linux then + try + let x11GLNative = fiImplementation.GetValue(window) + let window = unbox <| fiWindow.GetValue(x11GLNative) + + if window.Display <> 0n then + fCloseDisplay.Invoke(null, [| window.Display |]) |> ignore + with exn -> + Log.warn "[GL] Failed to close X11 display: %A" exn.Message + else + () /// /// creates a new context using the default configuration @@ -250,15 +300,14 @@ module ContextHandleOpenTK = | ValueNone -> context.MakeCurrent(null) window, context - - - let handle = new ContextHandle(context, window.WindowInfo) - handle.Initialize(debug, setDefaultStates = false) - // add the window to the windows-table to save it from being - // garbage collected. - if not <| windows.TryAdd(handle, window) then failwith "failed to add new context to live-set" - - handle + let dispose() = + context.Dispose() + window.Dispose() + X11.closeDisplay window + let handle = new ContextHandle(context, window.WindowInfo) + handle.OnDisposed.Add dispose + handle.Initialize(debug, setDefaultStates = false) + handle \ No newline at end of file diff --git a/src/Aardvark.Rendering.GL/Core/DebugOutput.fs b/src/Aardvark.Rendering.GL/Core/DebugOutput.fs index 254f6734..d5689d70 100644 --- a/src/Aardvark.Rendering.GL/Core/DebugOutput.fs +++ b/src/Aardvark.Rendering.GL/Core/DebugOutput.fs @@ -12,19 +12,37 @@ open Aardvark.Rendering.GL module Error = exception OpenGLException of ec : ErrorCode * msg : string with - override x.Message = sprintf "%A: %s" x.ec x.msg + override x.Message = $"{x.msg} (Error: {x.ec})" type GL with + static member private Check(str, throwOnError) = + let err = GL.GetError() + if err <> ErrorCode.NoError then + let str = $"{str}" + let msg = + if String.IsNullOrEmpty str then "An error occurred" + else string (Char.ToUpper str.[0]) + str.Substring(1) + + Report.Error($"[GL] {msg} (Error: {err})") + + if throwOnError then + raise <| OpenGLException(err, msg) + + /// Gets the value of the GL error flag and logs it + /// with the given message string if it is not equal to GL_NO_ERROR. + /// Throws an exception after logging if DebugConfig.ErrorFlagCheck = ThrowOnError. + /// Does nothing if DebugConfig.ErrorFlagCheck = Disabled. static member Check str = let mode = GL.CheckErrors if mode <> ErrorFlagCheck.Disabled then - let err = GL.GetError() - if err <> ErrorCode.NoError then - Report.Error("{0}: {1}", err, str) + let throwOnError = (mode = ErrorFlagCheck.ThrowOnError) + GL.Check(str, throwOnError) - if mode = ErrorFlagCheck.ThrowOnError then - raise <| OpenGLException(err, sprintf "%A" str) + /// Gets the value of the GL error flag and throws an exception + /// with the given message string if it is not equal to GL_NO_ERROR. + static member Assert str = + GL.Check(str, true) [] module private IGraphicsContextDebugExtensions = diff --git a/src/Aardvark.Rendering.GL/Core/Extensions/ARB_get_program_binary.fs b/src/Aardvark.Rendering.GL/Core/Extensions/ARB_get_program_binary.fs index 52369656..b03c131e 100644 --- a/src/Aardvark.Rendering.GL/Core/Extensions/ARB_get_program_binary.fs +++ b/src/Aardvark.Rendering.GL/Core/Extensions/ARB_get_program_binary.fs @@ -22,4 +22,20 @@ module ARB_get_program_binary = if GL.ARB_get_program_binary then GL.GetProgramBinary(program, bufSize, &length, &binaryFormat, binary) else - failwith "glGetProgramBinary is not available." \ No newline at end of file + failwith "glGetProgramBinary is not available." + + static member GetProgramBinary(program : int, length : int) = + let data : byte[] = Array.zeroCreate length + let mutable format = Unchecked.defaultof + let mutable returnedLength = length + GL.Dispatch.GetProgramBinary(program, length, &returnedLength, &format, data) + + if returnedLength = length then + data, format + else + null, format + + static member GetProgramBinaryLength(program : int) = + let mutable result = 0 + GL.GetProgram(program, GetProgramParameterName.ProgramBinaryLength, &result) + result \ No newline at end of file diff --git a/src/Aardvark.Rendering.GL/Core/ShaderCache.fs b/src/Aardvark.Rendering.GL/Core/ShaderCache.fs index a2b0ca72..5b366087 100644 --- a/src/Aardvark.Rendering.GL/Core/ShaderCache.fs +++ b/src/Aardvark.Rendering.GL/Core/ShaderCache.fs @@ -24,27 +24,45 @@ module internal ShaderCacheKeys = deviceCount : int } +type internal ShaderCacheEntry(surface : IBackendSurface, destroy : unit -> unit) = + member x.Surface = surface + member x.Dispose() = destroy() + + member x.Equals(other : ShaderCacheEntry) = + surface = other.Surface + + override x.Equals(other : obj) = + match other with + | :? ShaderCacheEntry as o -> x.Equals(o) + | _ -> false + + override x.GetHashCode() = + surface.GetHashCode() + + interface IDisposable with + member x.Dispose() = x.Dispose() + type internal ShaderCache() = - let codeCache = ConcurrentDictionary>() - let effectCache = ConcurrentDictionary>() + let codeCache = ConcurrentDictionary>() + let effectCache = ConcurrentDictionary>() - static let box (value : Error<'T>) : Error = + static let box (destroy : 'T -> unit) (value : Error<'T>) : Error = match value with - | Success v -> Success (v :> IBackendSurface) + | Success v -> Success (new ShaderCacheEntry(v, fun () -> destroy v)) | Error err -> Error err - static let unbox (value : Error) : Error<'T> = + static let unbox (value : Error) : Error<'T> = match value with - | Success v -> Success (unbox v) + | Success v -> Success (unbox v.Surface) | Error err -> Error err - member x.GetOrAdd<'T when 'T :> IBackendSurface>(key : CodeCacheKey, create : CodeCacheKey -> Error<'T>) : Error<'T> = - codeCache.GetOrAdd(key, create >> box) |> unbox + member x.GetOrAdd<'T when 'T :> IBackendSurface>(key : CodeCacheKey, create : CodeCacheKey -> Error<'T>, destroy : 'T -> unit) : Error<'T> = + codeCache.GetOrAdd(key, create >> box destroy) |> unbox - member x.GetOrAdd<'T when 'T :> IBackendSurface>(key : EffectCacheKey, create : EffectCacheKey -> Error<'T>) : Error<'T> = - effectCache.GetOrAdd(key, create >> box) |> unbox + member x.GetOrAdd<'T when 'T :> IBackendSurface>(key : EffectCacheKey, create : EffectCacheKey -> Error<'T>, destroy : 'T -> unit) : Error<'T> = + effectCache.GetOrAdd(key, create >> box destroy) |> unbox - member x.Programs = + member x.Entries = [ codeCache.Values; effectCache.Values ] |> Seq.concat |> Seq.choose (function @@ -54,7 +72,7 @@ type internal ShaderCache() = |> Seq.distinct member x.Dispose() = - for p in x.Programs do p.Dispose() + for e in x.Entries do e.Dispose() codeCache.Clear() effectCache.Clear() diff --git a/src/Aardvark.Rendering.GL/Core/Utilities/Common.fs b/src/Aardvark.Rendering.GL/Core/Utilities/Common.fs index 0ea654cc..ee795ba4 100644 --- a/src/Aardvark.Rendering.GL/Core/Utilities/Common.fs +++ b/src/Aardvark.Rendering.GL/Core/Utilities/Common.fs @@ -11,8 +11,13 @@ module private ErrorUtilities = let inline failf fmt = Printf.kprintf (fun str -> - Report.Error $"[GL] {str}" - failwith ("[GL] " + str) + let str = + if String.IsNullOrEmpty str then "An error occurred" + else string (Char.ToUpper str.[0]) + str.Substring(1) + + let msg = $"[GL] {str}" + Report.Error msg + failwith msg ) fmt [] diff --git a/src/Aardvark.Rendering.GL/Instructions/OpenGL.fs b/src/Aardvark.Rendering.GL/Instructions/OpenGL.fs index f06fdfe4..404c74bc 100644 --- a/src/Aardvark.Rendering.GL/Instructions/OpenGL.fs +++ b/src/Aardvark.Rendering.GL/Instructions/OpenGL.fs @@ -380,45 +380,43 @@ module OpenGl = /// let getProcAddressInternal (name : string) = match System.Environment.OSVersion with - | Linux -> - if opengl32 = 0n then - opengl32Lib <- DynamicLinker.loadLibrary "libGL.so.1" - opengl32 <- opengl32Lib.Handle + | Linux -> + if opengl32 = 0n then + opengl32Lib <- DynamicLinker.loadLibrary "libGL.so.1" + opengl32 <- opengl32Lib.Handle - match GLX.GetProcAddress name with - | 0n -> 0n - | ptr -> ptr - | Mac -> + match GLX.GetProcAddress name with + | 0n -> 0n + | ptr -> ptr - let ctx = OpenTK.Graphics.GraphicsContext.CurrentContext :?> OpenTK.Graphics.IGraphicsContextInternal + | Mac -> + let ctx = OpenTK.Graphics.GraphicsContext.CurrentContext :?> OpenTK.Graphics.IGraphicsContextInternal + ctx.GetAddress name - let handle = ctx.GetAddress name - if handle = 0n then printfn "could not grab: %s" name - handle - (*if opengl32 = 0n then - let ctx : OpenTK.Graphics.IGraphicsContextInternal = failwith "" + (*if opengl32 = 0n then + let ctx : OpenTK.Graphics.IGraphicsContextInternal = failwith "" - ctx.GetAddress(name) + ctx.GetAddress(name) - opengl32Lib <- DynamicLinker.loadLibrary "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib" - opengl32 <- opengl32Lib.Handle + opengl32Lib <- DynamicLinker.loadLibrary "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib" + opengl32 <- opengl32Lib.Handle - let handle = opengl32Lib.GetFunction(name).Handle - if handle <> 0n then Log.warn "could not get function ptr: %A" name - handle*) + let handle = opengl32Lib.GetFunction(name).Handle + if handle <> 0n then Log.warn "could not get function ptr: %A" name + handle*) - | Windows -> - if opengl32 = 0n then - opengl32Lib <- DynamicLinker.loadLibrary "Opengl32.dll" - opengl32 <- opengl32Lib.Handle + | Windows -> + if opengl32 = 0n then + opengl32Lib <- DynamicLinker.loadLibrary "Opengl32.dll" + opengl32 <- opengl32Lib.Handle - match Wgl.GetDefaultProcAddress name with - | 0n -> match Wgl.GLGetProcAddress name with - | 0n -> match Wgl.GetProcAddress(opengl32, name) with - | 0n -> 0n - | ptr -> ptr - | ptr -> ptr - | ptr -> ptr + match Wgl.GetDefaultProcAddress name with + | 0n -> match Wgl.GLGetProcAddress name with + | 0n -> match Wgl.GetProcAddress(opengl32, name) with + | 0n -> 0n + | ptr -> ptr + | ptr -> ptr + | ptr -> ptr let rec getProcAddressProbing (suffixes : list) (name : string) = diff --git a/src/Aardvark.Rendering.GL/Resources/Program.fs b/src/Aardvark.Rendering.GL/Resources/Program.fs index c90cad47..2a5272be 100644 --- a/src/Aardvark.Rendering.GL/Resources/Program.fs +++ b/src/Aardvark.Rendering.GL/Resources/Program.fs @@ -50,13 +50,22 @@ type Program = member x.WritesPointSize = FShade.GLSL.GLSLProgramInterface.usesPointSize x.Interface - member x.Dispose() = + // Deletes the program handle + // Called by the program cache of the Context + member x.Free() = using x.Context.ResourceLock (fun _ -> ResourceCounts.removeProgram x.Context GL.DeleteProgram(x.Handle) GL.Check "could not delete program" ) + member x.Dispose() = + // Programs are kept alive in the cache of the Context + // Disposing them manually leads to issues because they are not removed + // from the cache. As a workaround we just do nothing when Dispose() is called. + // The real solution is to use reference counting like in the Vulkan backend (breaking change). + () + interface IBackendSurface with member x.Handle = x.Handle :> obj member x.Dispose() = x.Dispose() @@ -435,6 +444,10 @@ module ProgramExtensions = GL.Check "could not create program" try + if GL.ARB_get_program_binary then + GL.ProgramParameter(handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1) + GL.Check "could not set program binary retrievable hint" + for s in shaders do GL.AttachShader(handle, s.Handle) GL.Check "could not attach shader to program" @@ -634,29 +647,28 @@ module ProgramExtensions = module Program = let tryGetBinary (program : Program) = - if GL.ARB_get_program_binary then - GL.GetError() |> ignore + if GL.ARB_get_program_binary && program.Context.NumProgramBinaryFormats > 0 then + let length = GL.Dispatch.GetProgramBinaryLength program.Handle + GL.Check "failed to get program binary length" - let mutable length = 0 - GL.GetProgram(program.Handle, GetProgramParameterName.ProgramBinaryLength, &length) - - let err = GL.GetError() - if err <> ErrorCode.NoError then - Log.warn "[GL] Failed to query program binary length: %A" err - None - else - let data : byte[] = Array.zeroCreate length - let mutable format = Unchecked.defaultof - GL.GetProgramBinary(program.Handle, length, &length, &format, data) + if length > 0 then + let data, format = GL.Dispatch.GetProgramBinary(program.Handle, length) + GL.Check "failed to get program binary" - let err = GL.GetError() - if err <> ErrorCode.NoError then - Log.warn "[GL] Failed to retrieve program binary: %A" err + if isNull data then + Log.warn "[GL] Failed to retrieve program binary" None else Some (format, data) + else + Log.warn "[GL] Program binary length is zero bytes" + None else - Report.Line(4, "[GL] Cannot read shader cache because GL_ARB_get_program_binary is not supported") + let reason = + if GL.ARB_get_program_binary then "no binary formats are supported" + else "GL_ARB_get_program_binary is not supported" + + Report.Line(4, $"[GL] Cannot read shader cache because {reason}") None let ofShaderCacheEntry (context : Context) (fixBindings : bool) (entry : ShaderCacheEntry) = @@ -763,8 +775,23 @@ module ProgramExtensions = None ) + [] + module internal ShaderCacheExtensions = + type ShaderCache with + member inline x.GetOrAdd(key : CodeCacheKey, create : CodeCacheKey -> Error) = + x.GetOrAdd(key, create, fun p -> p.Free()) + + member inline x.GetOrAdd(key : EffectCacheKey, create : EffectCacheKey -> Error) = + x.GetOrAdd(key, create, fun p -> p.Free()) + type Aardvark.Rendering.GL.Context with + /// Returns whether the inputs gl_Layer and gl_ViewportIndex can be used + /// in fragment shaders. If not a custom output / input must be used for + /// layered effects. + member internal x.SupportsLayeredEffects = + x.Driver.glsl >= Version(4, 3, 0) + member x.TryGetProgramBinary(prog : Program) = use __ = prog.Context.ResourceLock Program.tryGetBinary prog @@ -856,7 +883,7 @@ module ProgramExtensions = let glsl = lazy ( try - let module_ = key.layout.Link(key.effect, key.deviceCount, Range1d(-1.0, 1.0), false, key.topology) + let module_ = key.layout.Link(key.effect, key.deviceCount, Range1d(-1.0, 1.0), false, key.topology, not x.SupportsLayeredEffects) ModuleCompiler.compileGLSL x.FShadeBackend module_ with exn -> Log.error "%s" exn.Message diff --git a/src/Aardvark.Rendering.GL/Resources/Textures/Image.fs b/src/Aardvark.Rendering.GL/Resources/Textures/Image.fs new file mode 100644 index 00000000..d31ba04a --- /dev/null +++ b/src/Aardvark.Rendering.GL/Resources/Textures/Image.fs @@ -0,0 +1,141 @@ +namespace Aardvark.Rendering.GL + +open Aardvark.Base +open Aardvark.Rendering +open OpenTK.Graphics.OpenGL4 +open Aardvark.Rendering.GL + +[] +type internal Image = + | Texture of Texture + | Renderbuffer of Renderbuffer + + member x.Handle = + match x with + | Texture t -> t.Handle + | Renderbuffer rb -> rb.Handle + + member x.Dimension = + match x with + | Texture t -> t.Dimension + | Renderbuffer _ -> TextureDimension.Texture2D + + member x.Format = + match x with + | Texture t -> t.Format + | Renderbuffer rb -> rb.Format + + member x.Target = + match x with + | Texture t -> unbox <| TextureTarget.ofTexture t + | Renderbuffer _ -> ImageTarget.Renderbuffer + + member x.GetSize(level : int) = + match x with + | Texture t -> t.GetSize(level) + | Renderbuffer rb -> V3i(rb.Size, 1) + + member x.Samples = + match x with + | Texture t -> t.Multisamples + | Renderbuffer rb -> rb.Samples + + member x.IsMultisampled = + x.Samples > 1 + + member private x.IsDepth = + match x with + | Texture t -> t.Format.IsDepth + | Renderbuffer rb -> rb.Format.IsDepth + + member private x.IsStencil = + match x with + | Texture _ -> false + | Renderbuffer rb -> rb.Format.IsStencil + + member private x.IsDepthStencil = + match x with + | Texture t -> t.Format.IsDepthStencil + | Renderbuffer rb -> rb.Format.IsDepthStencil + + member x.Attachment = + if x.IsDepth then FramebufferAttachment.DepthAttachment + elif x.IsStencil then FramebufferAttachment.StencilAttachment + elif x.IsDepthStencil then FramebufferAttachment.DepthStencilAttachment + else FramebufferAttachment.ColorAttachment0 + + member x.Mask = + if x.IsDepth then ClearBufferMask.DepthBufferBit + elif x.IsStencil then ClearBufferMask.StencilBufferBit + elif x.IsDepthStencil then ClearBufferMask.DepthBufferBit ||| ClearBufferMask.StencilBufferBit + else ClearBufferMask.ColorBufferBit + + member x.WindowOffset(level : int, window : Box3i) = + match x with + | Image.Texture t -> t.WindowOffset(level, window) + | Image.Renderbuffer rb -> window |> WindowOffset.flipY rb.Size.Y + + member x.WindowOffset(level : int, offset : V3i, size : V3i) = + x.WindowOffset(level, Box3i.FromMinAndSize(offset, size)) + + member x.WindowOffset(level : int, window : Box2i) = + let window = Box3i(V3i(window.Min, 0), V3i(window.Max, 1)) + x .WindowOffset(level, window).XY + + member x.WindowOffset(level : int, offset : V2i, size : V2i) = + x.WindowOffset(level, Box2i.FromMinAndSize(offset, size)) + +module internal Image = + + /// Attaches an image to the framebuffer bound at the given framebuffer target + let attach (framebufferTarget : FramebufferTarget) (level : int) (slice : int) (image : Image) = + let attachment = image.Attachment + + match image with + | Image.Texture texture -> + let target = texture |> TextureTarget.ofTexture + let targetSlice = target |> TextureTarget.toSliceTarget slice + + match texture.Dimension, texture.IsArray with + | TextureDimension.Texture1D, true + | TextureDimension.Texture2D, true + | TextureDimension.TextureCube, true + | TextureDimension.Texture3D, false -> + GL.FramebufferTextureLayer(framebufferTarget, attachment, texture.Handle, level, slice) + + | TextureDimension.Texture1D, false -> + GL.FramebufferTexture1D(framebufferTarget, attachment, targetSlice, texture.Handle, level) + + | TextureDimension.Texture2D, false + | TextureDimension.TextureCube, false -> + GL.FramebufferTexture2D(framebufferTarget, attachment, targetSlice, texture.Handle, level) + + | d, a -> + failwithf "[GL] cannot attach %A%s to framebuffer" d (if a then "[]" else "") + + | Image.Renderbuffer renderBuffer -> + GL.FramebufferRenderbuffer(framebufferTarget, attachment, RenderbufferTarget.Renderbuffer, renderBuffer.Handle) + + GL.Check "could not attach texture to framebuffer" + + /// Uses a framebuffer to the read the image layers of the given level from slice baseSlice to baseSlice + slices. + let readLayers (image : Image) (level : int) (baseSlice : int) (slices : int) (f : int -> unit) = + let attachment = image.Attachment + + let readBuffer = + if attachment = FramebufferAttachment.ColorAttachment0 then ReadBufferMode.ColorAttachment0 + else ReadBufferMode.None + + Framebuffer.temporary FramebufferTarget.ReadFramebuffer (fun fbo -> + GL.ReadBuffer(readBuffer) + GL.Check "could not set buffer" + + try + for slice = baseSlice to baseSlice + slices - 1 do + image |> attach FramebufferTarget.ReadFramebuffer level slice + Framebuffer.check FramebufferTarget.ReadFramebuffer + f slice + + finally + GL.ReadBuffer(ReadBufferMode.None) + ) \ No newline at end of file diff --git a/src/Aardvark.Rendering.GL/Resources/Textures/Renderbuffer.fs b/src/Aardvark.Rendering.GL/Resources/Textures/Renderbuffer.fs index 08c9324b..c9361e9f 100644 --- a/src/Aardvark.Rendering.GL/Resources/Textures/Renderbuffer.fs +++ b/src/Aardvark.Rendering.GL/Resources/Textures/Renderbuffer.fs @@ -55,32 +55,32 @@ type Renderbuffer = new (ctx : Context, handle : int, size : V2i, format : TextureFormat, samples : int, sizeInBytes : int64) = { Context = ctx; Handle = handle; Size = size; Format = format; Samples = samples; SizeInBytes = sizeInBytes } + + new (ctx : Context, handle : int, size : V2i, format : TextureFormat, samples : int) = + let sizeInBytes = ResourceCounts.texSizeInBytes TextureDimension.Texture2D size.XYI format samples 1 1 + new Renderbuffer(ctx, handle, size, format, samples, sizeInBytes) end [] module RenderbufferExtensions = - let private updateRenderbuffer (cxt : Context) (handle : int) (size : V2i) (format : TextureFormat) (samples : int) = - GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, handle) - GL.Check "could not bind renderbuffer" + let private updateRenderbuffer (ctx : Context) (handle : int) (size : V2i) (format : TextureFormat) (samples : int) = + if Vec.anyGreater size ctx.MaxRenderbufferSize then + failf $"cannot create renderbuffer with size {size} (maximum is {ctx.MaxRenderbufferSize})" let samples = - if samples > 1 then - let counts = cxt.GetFormatSamples(ImageTarget.Renderbuffer, format) - if counts.Contains samples then samples - else - let max = Set.maxElement counts - Log.warn "[GL] cannot create %A render buffer with %d samples (using %d instead)" format samples max - max - else - 1 + Image.validateSampleCount ctx ImageTarget.Renderbuffer format samples + + GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, handle) + GL.Check "could not bind renderbuffer" if samples > 1 then GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, samples, TextureFormat.toRenderbufferStorage format, size.X, size.Y) else GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, TextureFormat.toRenderbufferStorage format, size.X, size.Y) - GL.Check "could not set renderbuffer storage" + + GL.Check $"failed to allocate renderbuffer storage (format = {format}, size = {size}, samples = {samples})" GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0) GL.Check "could not unbind renderbuffer" @@ -92,15 +92,14 @@ module RenderbufferExtensions = let samples = defaultArg samples 1 using x.ResourceLock (fun _ -> - let handle = GL.GenRenderbuffer() GL.Check "could not create renderbuffer" let samples = updateRenderbuffer x handle size format samples - let sizeInBytes = (int64 size.X * int64 size.Y * int64 format.PixelSizeInBits) / 8L - ResourceCounts.addRenderbuffer x sizeInBytes - new Renderbuffer(x, handle, size, format, samples, sizeInBytes) + let rb = new Renderbuffer(x, handle, size, format, samples) + ResourceCounts.addRenderbuffer x rb.SizeInBytes + rb ) member x.Update(r : Renderbuffer, size : V2i, format : TextureFormat, ?samples : int) = @@ -109,7 +108,7 @@ module RenderbufferExtensions = if r.Size <> size || r.Format <> format || r.Samples <> samples then using x.ResourceLock (fun _ -> let samples = updateRenderbuffer x r.Handle size format samples - let sizeInBytes = (int64 size.X * int64 size.Y * int64 format.PixelSizeInBits) / 8L + let sizeInBytes = ResourceCounts.texSizeInBytes TextureDimension.Texture2D size.XYI format samples 1 1 ResourceCounts.resizeRenderbuffer x r.SizeInBytes sizeInBytes r.SizeInBytes <- sizeInBytes diff --git a/src/Aardvark.Rendering.GL/Resources/Textures/Texture.fs b/src/Aardvark.Rendering.GL/Resources/Textures/Texture.fs index d50d1ddd..6a1f6e2e 100644 --- a/src/Aardvark.Rendering.GL/Resources/Textures/Texture.fs +++ b/src/Aardvark.Rendering.GL/Resources/Textures/Texture.fs @@ -32,14 +32,29 @@ module internal TextureResourceCounts = let updateTexture (ctx:Context) oldSize newSize = Interlocked.Add(&ctx.MemoryUsage.TextureMemory,newSize-oldSize) |> ignore - let texSizeInBytes (size : V3i, t : TextureFormat, samples : int, levels : int) = - let pixelCount = (int64 size.X) * (int64 size.Y) * (int64 size.Z) * (int64 samples) - let mutable size = pixelCount * (int64 t.PixelSizeInBits) / 8L - let mutable temp = size - for i in 1..levels-1 do - temp <- temp >>> 2 - size <- size + temp - size + /// Computes an estimate of the memory usage of a texture with the given parameters. + /// Assumes that per-pixel size in bits is a power of two, so for example RGB textures are layed out as RGBX. + let texSizeInBytes (dimension : TextureDimension) (size : V3i) (format : TextureFormat) (samples : int) (levels : int) (count : int) = + let count = + if dimension = TextureDimension.TextureCube then 6 * count + else count + + let getLevelSizeInBytes : V3i -> int64 = + match format.CompressionMode with + | CompressionMode.None -> + let bitsPerPixel = format.PixelSizeInBits |> Fun.NextPowerOfTwo |> max 8 |> int64 + fun size -> (int64 size.X) * (int64 size.Y) * (int64 size.Z) * (int64 samples) * (bitsPerPixel / 8L) + + | mode -> + fun size -> int64 <| CompressionMode.sizeInBytes size mode + + let mutable layerSizeInBytes = 0L + + for level = 0 to levels - 1 do + let levelSize = Fun.MipmapLevelSize(size, level) + layerSizeInBytes <- layerSizeInBytes + getLevelSizeInBytes levelSize + + layerSizeInBytes * (int64 count) type Texture = class @@ -98,6 +113,11 @@ type Texture = Format = format SizeInBytes = sizeInBytes } + new(ctx : Context, handle : int, dimension : TextureDimension, mipMapLevels : int, multisamples : int, + size : V3i, count : int, isArray : bool, format : TextureFormat) = + let sizeInBytes = ResourceCounts.texSizeInBytes dimension size format multisamples mipMapLevels count + new Texture(ctx, handle, dimension, mipMapLevels, multisamples, size, count, isArray, format, sizeInBytes) + new(ctx : Context, handle : int, dimension : TextureDimension, mipMapLevels : int, multisamples : int, size : V3i, count : Option, format : TextureFormat, sizeInBytes : int64) = let cnt, isArray = @@ -106,6 +126,15 @@ type Texture = | None -> 1, false new Texture(ctx, handle, dimension, mipMapLevels, multisamples, size, cnt, isArray, format, sizeInBytes) + + new(ctx : Context, handle : int, dimension : TextureDimension, mipMapLevels : int, multisamples : int, + size : V3i, count : Option, format : TextureFormat) = + let cnt, isArray = + match count with + | Some cnt -> cnt, true + | None -> 1, false + + new Texture(ctx, handle, dimension, mipMapLevels, multisamples, size, cnt, isArray, format) end type internal SharedTexture(ctx : Context, handle : int, external : IExportedBackendTexture, memory : SharedMemoryBlock) = @@ -154,6 +183,26 @@ module internal TextureUtilitiesAndExtensions = IsMultisampled = x.isMS IsArray = x.isArray } + module Image = + + /// Validates the sample count for the given image parameters and returns an appropiate fallback + /// if the sample count is not supported. + let validateSampleCount (ctx : Context) (target : ImageTarget) (format : TextureFormat) (samples : int) = + if samples > 1 then + let counts = ctx.GetFormatSamples(target, format) + if counts.Contains samples then samples + else + let fallback = + counts + |> Set.toList + |> List.sortBy ((-) samples >> abs) + |> List.head + + Log.warn "[GL] cannot create %A image with %d samples (using %d instead)" format samples fallback + fallback + else + 1 + [] module TensorExtensions = @@ -195,7 +244,7 @@ module internal TextureUtilitiesAndExtensions = member x.AsBytes(elementSize) = x |> Tensor4Info.asBytes elementSize member x.AsBytes<'T>() = x.AsBytes(sizeof<'T>) - module private WindowOffset = + module WindowOffset = let flipY (height : int) (window : Box3i) = V3i(window.Min.X, height - window.Max.Y, window.Min.Z) @@ -250,143 +299,6 @@ module internal TextureUtilitiesAndExtensions = GL.BindFramebuffer(target, old) GL.DeleteFramebuffer(fbo) - - [] - type Image = - | Texture of Texture - | Renderbuffer of Renderbuffer - - member x.Handle = - match x with - | Texture t -> t.Handle - | Renderbuffer rb -> rb.Handle - - member x.Dimension = - match x with - | Texture t -> t.Dimension - | Renderbuffer _ -> TextureDimension.Texture2D - - member x.Format = - match x with - | Texture t -> t.Format - | Renderbuffer rb -> rb.Format - - member x.Target = - match x with - | Texture t -> unbox <| TextureTarget.ofTexture t - | Renderbuffer _ -> ImageTarget.Renderbuffer - - member x.GetSize(level : int) = - match x with - | Texture t -> t.GetSize(level) - | Renderbuffer rb -> V3i(rb.Size, 1) - - member x.Samples = - match x with - | Texture t -> t.Multisamples - | Renderbuffer rb -> rb.Samples - - member x.IsMultisampled = - x.Samples > 1 - - member private x.IsDepth = - match x with - | Texture t -> t.Format.IsDepth - | Renderbuffer rb -> rb.Format.IsDepth - - member private x.IsStencil = - match x with - | Texture _ -> false - | Renderbuffer rb -> rb.Format.IsStencil - - member private x.IsDepthStencil = - match x with - | Texture t -> t.Format.IsDepthStencil - | Renderbuffer rb -> rb.Format.IsDepthStencil - - member x.Attachment = - if x.IsDepth then FramebufferAttachment.DepthAttachment - elif x.IsStencil then FramebufferAttachment.StencilAttachment - elif x.IsDepthStencil then FramebufferAttachment.DepthStencilAttachment - else FramebufferAttachment.ColorAttachment0 - - member x.Mask = - if x.IsDepth then ClearBufferMask.DepthBufferBit - elif x.IsStencil then ClearBufferMask.StencilBufferBit - elif x.IsDepthStencil then ClearBufferMask.DepthBufferBit ||| ClearBufferMask.StencilBufferBit - else ClearBufferMask.ColorBufferBit - - member x.WindowOffset(level : int, window : Box3i) = - match x with - | Image.Texture t -> t.WindowOffset(level, window) - | Image.Renderbuffer rb -> window |> WindowOffset.flipY rb.Size.Y - - member x.WindowOffset(level : int, offset : V3i, size : V3i) = - x.WindowOffset(level, Box3i.FromMinAndSize(offset, size)) - - member x.WindowOffset(level : int, window : Box2i) = - let window = Box3i(V3i(window.Min, 0), V3i(window.Max, 1)) - x .WindowOffset(level, window).XY - - member x.WindowOffset(level : int, offset : V2i, size : V2i) = - x.WindowOffset(level, Box2i.FromMinAndSize(offset, size)) - - module Image = - - /// Attaches an image to the framebuffer bound at the given framebuffer target - let attach (framebufferTarget : FramebufferTarget) (level : int) (slice : int) (image : Image) = - let attachment = image.Attachment - - match image with - | Image.Texture texture -> - let target = texture |> TextureTarget.ofTexture - let targetSlice = target |> TextureTarget.toSliceTarget slice - - match texture.Dimension, texture.IsArray with - | TextureDimension.Texture1D, true - | TextureDimension.Texture2D, true - | TextureDimension.TextureCube, true - | TextureDimension.Texture3D, false -> - GL.FramebufferTextureLayer(framebufferTarget, attachment, texture.Handle, level, slice) - - | TextureDimension.Texture1D, false -> - GL.FramebufferTexture1D(framebufferTarget, attachment, targetSlice, texture.Handle, level) - - | TextureDimension.Texture2D, false - | TextureDimension.TextureCube, false -> - GL.FramebufferTexture2D(framebufferTarget, attachment, targetSlice, texture.Handle, level) - - | d, a -> - failwithf "[GL] cannot attach %A%s to framebuffer" d (if a then "[]" else "") - - | Image.Renderbuffer renderBuffer -> - GL.FramebufferRenderbuffer(framebufferTarget, attachment, RenderbufferTarget.Renderbuffer, renderBuffer.Handle) - - GL.Check "could not attach texture to framebuffer" - - /// Uses a framebuffer to the read the image layers of the given level from slice baseSlice to baseSlice + slices. - let readLayers (image : Image) (level : int) (baseSlice : int) (slices : int) (f : int -> unit) = - let attachment = image.Attachment - - let readBuffer = - if attachment = FramebufferAttachment.ColorAttachment0 then ReadBufferMode.ColorAttachment0 - else ReadBufferMode.None - - Framebuffer.temporary FramebufferTarget.ReadFramebuffer (fun fbo -> - GL.ReadBuffer(readBuffer) - GL.Check "could not set buffer" - - try - for slice = baseSlice to baseSlice + slices - 1 do - image |> attach FramebufferTarget.ReadFramebuffer level slice - Framebuffer.check FramebufferTarget.ReadFramebuffer - f slice - - finally - GL.ReadBuffer(ReadBufferMode.None) - ) - - [] module TextureCreationExtensions = @@ -428,6 +340,27 @@ module TextureCreationExtensions = type Context with + // ================================================================================================================ + // SetDefaultTextureParams + // ================================================================================================================ + + member internal x.SetDefaultTextureParams(target : TextureTarget, format : TextureFormat, mipMapLevels : int) = + match target with + | TextureTarget.Texture2DMultisample + | TextureTarget.Texture2DMultisampleArray -> () + | _ -> + // For gray scale textures, duplicate channel + if TextureFormat.toColFormat format = Col.Format.Gray then + GL.TexParameter(target, TextureParameterName.TextureSwizzleG, int PixelFormat.Red) + GL.TexParameter(target, TextureParameterName.TextureSwizzleB, int PixelFormat.Red) + + GL.TexParameter(target, TextureParameterName.TextureMaxLevel, mipMapLevels - 1) + GL.TexParameter(target, TextureParameterName.TextureBaseLevel, 0) + GL.TexParameter(target, TextureParameterName.TextureWrapS, int TextureWrapMode.ClampToEdge) + GL.TexParameter(target, TextureParameterName.TextureWrapT, int TextureWrapMode.ClampToEdge) + GL.TexParameter(target, TextureParameterName.TextureMinFilter, int TextureMinFilter.Linear) + GL.TexParameter(target, TextureParameterName.TextureMagFilter, int TextureMagFilter.Linear) + // ================================================================================================================ // CreateTexture // ================================================================================================================ @@ -437,7 +370,7 @@ module TextureCreationExtensions = let isArray = slices > 0 if format = TextureFormat.StencilIndex8 && not GL.ARB_texture_stencil8 then - failwithf "[GL] textures with format %A not supported" format + failf "textures with format %A not supported" format match dim, isArray with | TextureDimension.Texture1D, false -> x.CreateTexture1D(size.X, levels, format) @@ -448,383 +381,222 @@ module TextureCreationExtensions = | TextureDimension.Texture3D, true -> raise <| ArgumentException("3D textures cannot be arrayed") | TextureDimension.TextureCube, false -> x.CreateTextureCube(size.X, levels, format) | TextureDimension.TextureCube, true -> x.CreateTextureCubeArray(size.X, slices, levels, format) - | _ -> failwith "[GL] Invalid texture dimension" + | _ -> failf "Invalid texture dimension" ) member x.CreateTexture1D(size : int, mipMapLevels : int, format : TextureFormat) = using x.ResourceLock (fun _ -> - let h = GL.GenTexture() - GL.Check "could not create texture" - - ResourceCounts.addTexture x 0L - let tex = new Texture(x, h, TextureDimension.Texture1D, mipMapLevels, 1, V3i.Zero, None, format, 0L) - x.UpdateTexture1D(tex, size, mipMapLevels, format) - - tex - ) + if size > x.MaxTextureSize.X then + failf $"cannot create 1D texture with size {size} (maximum is {x.MaxTextureSize.X})" - member x.CreateTexture2D(size : V2i, mipMapLevels : int, format : TextureFormat, samples : int) = - using x.ResourceLock (fun _ -> let h = GL.GenTexture() GL.Check "could not create texture" - ResourceCounts.addTexture x 0L - let tex = new Texture(x, h, TextureDimension.Texture2D, mipMapLevels, 1, V3i.Zero, None, format, 0L) - - x.UpdateTexture2D(tex, size, mipMapLevels, format, samples) + GL.BindTexture(TextureTarget.Texture1D, h) + GL.Check "could not bind texture" - tex - ) + x.SetDefaultTextureParams(TextureTarget.Texture1D, format, mipMapLevels) + GL.Check "could not set default texture parameters" - member x.CreateTexture3D(size : V3i, mipMapLevels : int, format : TextureFormat) = - using x.ResourceLock (fun _ -> - let h = GL.GenTexture() - GL.Check "could not create texture" + GL.Dispatch.TexStorage1D(TextureTarget1d.Texture1D, mipMapLevels, TextureFormat.toSizedInternalFormat format, size) + GL.Check $"failed to allocate 1D texture storage (format = {format}, size = {size}, levels = {mipMapLevels})" - ResourceCounts.addTexture x 0L - let tex = new Texture(x, h, TextureDimension.Texture3D, mipMapLevels, 1, V3i.Zero, None, format, 0L) - x.UpdateTexture3D(tex, size, mipMapLevels, format) + GL.BindTexture(TextureTarget.Texture1D, 0) + GL.Check "could not unbind texture" + let tex = new Texture(x, h, TextureDimension.Texture1D, mipMapLevels, 1, V3i(size, 1, 1), None, format) + ResourceCounts.addTexture x tex.SizeInBytes tex ) - member x.CreateTextureCube(size : int, mipMapLevels : int, format : TextureFormat) = + member x.CreateTexture2D(size : V2i, mipMapLevels : int, format : TextureFormat, samples : int) = using x.ResourceLock (fun _ -> - let h = GL.GenTexture() - GL.Check "could not create texture" + if Vec.anyGreater size x.MaxTextureSize then + failf $"cannot create 2D texture with size {size} (maximum is {x.MaxTextureSize})" - ResourceCounts.addTexture x 0L - let tex = new Texture(x, h, TextureDimension.TextureCube, mipMapLevels, 1, V3i(size, size, 0), None, format, 0L) - x.UpdateTextureCube(tex, size, mipMapLevels, format) + let samples = + if samples <= 1 then 1 + else Image.validateSampleCount x ImageTarget.Texture2DMultisample format samples - tex - ) + let target = + if samples = 1 then TextureTarget.Texture2D + else TextureTarget.Texture2DMultisample - member x.CreateTexture1DArray(size : int, count : int, mipMapLevels : int, format : TextureFormat) = - using x.ResourceLock (fun _ -> let h = GL.GenTexture() GL.Check "could not create texture" - ResourceCounts.addTexture x 0L - let tex = new Texture(x, h, TextureDimension.Texture1D, mipMapLevels, 1, V3i.Zero, Some count, format, 0L) - x.UpdateTexture1DArray(tex, size, count, mipMapLevels, format) + GL.BindTexture(target, h) + GL.Check "could not bind texture" - tex - ) + x.SetDefaultTextureParams(target, format, mipMapLevels) + GL.Check "could not set default texture parameters" - member x.CreateTexture2DArray(size : V2i, count : int, mipMapLevels : int, format : TextureFormat, samples : int) = - using x.ResourceLock (fun _ -> - let h = GL.GenTexture() - GL.Check "could not create texture" + let ifmt = TextureFormat.toSizedInternalFormat format - ResourceCounts.addTexture x 0L - let tex = new Texture(x, h, TextureDimension.Texture2D, mipMapLevels, 1, V3i.Zero, Some count, format, 0L) + if samples = 1 then + GL.Dispatch.TexStorage2D(unbox target, mipMapLevels, ifmt, size.X, size.Y) + GL.Check $"failed to allocate 2D texture storage (format = {format}, size = {size}, levels = {mipMapLevels})" + else + GL.Dispatch.TexStorage2DMultisample(unbox target, samples, ifmt, size.X, size.Y, true) + GL.Check $"failed to allocate 2D texture storage (format = {format}, size = {size}, samples = {samples})" - x.UpdateTexture2DArray(tex, size, count, mipMapLevels, format, samples) + GL.BindTexture(target, 0) + GL.Check "could not unbind texture" + let tex = new Texture(x, h, TextureDimension.Texture2D, mipMapLevels, samples, size.XYI, None, format) + ResourceCounts.addTexture x tex.SizeInBytes tex ) - member x.CreateTextureCubeArray(size : int, count : int, mipMapLevels : int, format : TextureFormat) = + member x.CreateTexture3D(size : V3i, mipMapLevels : int, format : TextureFormat) = using x.ResourceLock (fun _ -> + if Vec.anyGreater size x.MaxTextureSize3D then + failf $"cannot create 3D texture with size {size} (maximum is {x.MaxTextureSize3D})" + let h = GL.GenTexture() GL.Check "could not create texture" - ResourceCounts.addTexture x 0L - let tex = new Texture(x, h, TextureDimension.TextureCube, mipMapLevels, 1, V3i(size, size, 0), Some count, format, 0L) - x.UpdateTextureCubeArray(tex, size, count, mipMapLevels, format) - - tex - ) - - - // ================================================================================================================ - // AllocateTexture - // ================================================================================================================ - - member internal x.SetDefaultTextureParams(target : TextureTarget, format : TextureFormat, mipMapLevels : int) = - // For gray scale textures, duplicate channel - if TextureFormat.toColFormat format = Col.Format.Gray then - GL.TexParameter(target, TextureParameterName.TextureSwizzleG, int PixelFormat.Red) - GL.TexParameter(target, TextureParameterName.TextureSwizzleB, int PixelFormat.Red) - - match target with - | TextureTarget.Texture2DMultisample - | TextureTarget.Texture2DMultisampleArray -> () - | _ -> - GL.TexParameter(target, TextureParameterName.TextureMaxLevel, mipMapLevels - 1) - GL.TexParameter(target, TextureParameterName.TextureBaseLevel, 0) - GL.TexParameter(target, TextureParameterName.TextureWrapS, int TextureWrapMode.ClampToEdge) - GL.TexParameter(target, TextureParameterName.TextureWrapT, int TextureWrapMode.ClampToEdge) - GL.TexParameter(target, TextureParameterName.TextureMinFilter, int TextureMinFilter.Linear) - GL.TexParameter(target, TextureParameterName.TextureMagFilter, int TextureMagFilter.Linear) - - member private x.ValidateAndAllocateTexture(target : TextureTarget, size : 'T, create : TextureTarget -> unit) = - // Allocate using proxy target - let proxyTarget = TextureTarget.toProxy target - create proxyTarget - - // Check for success - let mutable width = 0 - GL.GetTexLevelParameter(proxyTarget, 0, GetTextureParameter.TextureWidth, &width) - GL.Check "could not get texture parameter" - - if width = 0 then - failwithf "[GL] cannot create a texture with size %A as it exceeds device limits" size - - // Allocate for real using regular target - create target - - member inline private x.AllocateTexture1D(target : TextureTarget, mipMapLevels : int, format : SizedInternalFormat, size : int) = - x.SetDefaultTextureParams(target, unbox format, mipMapLevels) - - x.ValidateAndAllocateTexture(target, size, fun target -> - GL.Dispatch.TexStorage1D(unbox target, mipMapLevels, format, size) - ) - - member inline private x.AllocateTexture2D(target : TextureTarget, mipMapLevels : int, format : SizedInternalFormat, size : V2i) = - x.SetDefaultTextureParams(target, unbox format, mipMapLevels) - - x.ValidateAndAllocateTexture(target, size, fun target -> - GL.Dispatch.TexStorage2D(unbox target, mipMapLevels, format, size.X, size.Y) - ) - - member inline private x.AllocateTexture2DMultisample(target : TextureTarget, samples : int, - format : SizedInternalFormat, size : V2i, fixedSampleLocations : bool) = - x.SetDefaultTextureParams(target, unbox format, 1) - - x.ValidateAndAllocateTexture(target, size, fun target -> - GL.Dispatch.TexStorage2DMultisample(unbox target, samples, format, size.X, size.Y, fixedSampleLocations) - ) - - member inline private x.AllocateTexture3D(target : TextureTarget, mipMapLevels : int, format : SizedInternalFormat, size : V3i) = - x.SetDefaultTextureParams(target, unbox format, mipMapLevels) - - x.ValidateAndAllocateTexture(target, size, fun target -> - GL.Dispatch.TexStorage3D(unbox target, mipMapLevels, format, size.X, size.Y, size.Z) - ) - - member inline private x.AllocateTexture3DMultisample(target : TextureTarget, samples : int, - format : SizedInternalFormat, size : V3i, fixedSampleLocations : bool) = - x.SetDefaultTextureParams(target, unbox format, 1) - - x.ValidateAndAllocateTexture(target, size, fun target -> - GL.Dispatch.TexStorage3DMultisample(unbox target, samples, format, size.X, size.Y, size.Z, fixedSampleLocations) - ) - - - // ================================================================================================================ - // UpdateTexture - // ================================================================================================================ - - member private x.UpdateTexture1D(tex : Texture, size : int, mipMapLevels : int, format : TextureFormat) = - using x.ResourceLock (fun _ -> - GL.BindTexture(TextureTarget.Texture1D, tex.Handle) + GL.BindTexture(TextureTarget.Texture3D, h) GL.Check "could not bind texture" - x.AllocateTexture1D(TextureTarget.Texture1D, mipMapLevels, TextureFormat.toSizedInternalFormat format, size) - GL.Check "could not allocate texture" + x.SetDefaultTextureParams(TextureTarget.Texture3D, format, mipMapLevels) + GL.Check "could not set default texture parameters" - GL.BindTexture(TextureTarget.Texture1D, 0) - GL.Check "could not unbind texture" + GL.Dispatch.TexStorage3D(TextureTarget3d.Texture3D, mipMapLevels, TextureFormat.toSizedInternalFormat format, size.X, size.Y, size.Z) + GL.Check $"failed to allocate 3D texture storage (format = {format}, size = {size}, levels = {mipMapLevels})" - let sizeInBytes = ResourceCounts.texSizeInBytes(V3i(size, 1, 1), format, 1, mipMapLevels) - ResourceCounts.updateTexture tex.Context tex.SizeInBytes sizeInBytes - tex.SizeInBytes <- sizeInBytes + GL.BindTexture(TextureTarget.Texture3D, 0) + GL.Check "could not unbind texture" - tex.MipMapLevels <- mipMapLevels - tex.Dimension <- TextureDimension.Texture1D - tex.Size <- V3i(size, 1, 1) - tex.Format <- format + let tex = new Texture(x, h, TextureDimension.Texture3D, mipMapLevels, 1, size, None, format) + ResourceCounts.addTexture x tex.SizeInBytes + tex ) - member private x.UpdateTexture2D(tex : Texture, size : V2i, mipMapLevels : int, format : TextureFormat, samples : int) = - let ifmt = TextureFormat.toSizedInternalFormat format - + member x.CreateTextureCube(size : int, mipMapLevels : int, format : TextureFormat) = using x.ResourceLock (fun _ -> - let target = - if samples = 1 then TextureTarget.Texture2D - else TextureTarget.Texture2DMultisample + if size > x.MaxTextureSizeCube then + failf $"cannot create cube texture with size {size} (maximum is {x.MaxTextureSizeCube})" - let samples = - if samples > 1 then - let counts = x.GetFormatSamples(unbox target, format) - if counts.Contains samples then samples - else - let max = Set.maxElement counts - Log.warn "[GL] cannot create %A texture with %d samples (using %d instead)" format samples max - max - else - 1 + let h = GL.GenTexture() + GL.Check "could not create texture" - GL.BindTexture(target, tex.Handle) + GL.BindTexture(TextureTarget.TextureCubeMap, h) GL.Check "could not bind texture" - if samples = 1 then - x.AllocateTexture2D(target, mipMapLevels, ifmt, size) - else - x.AllocateTexture2DMultisample(target, samples, ifmt, size, true) + x.SetDefaultTextureParams(TextureTarget.TextureCubeMap, format, mipMapLevels) + GL.Check "could not set default texture parameters" - GL.Check "could not allocate texture" + GL.Dispatch.TexStorage2D(TextureTarget2d.TextureCubeMap, mipMapLevels, TextureFormat.toSizedInternalFormat format, size, size) + GL.Check $"failed to allocate cube texture storage (format = {format}, size = {size}, levels = {mipMapLevels})" - GL.BindTexture(target, 0) + GL.BindTexture(TextureTarget.TextureCubeMap, 0) GL.Check "could not unbind texture" - let sizeInBytes = ResourceCounts.texSizeInBytes(size.XYI, format, samples, mipMapLevels) - ResourceCounts.updateTexture tex.Context tex.SizeInBytes sizeInBytes - tex.SizeInBytes <- sizeInBytes - - tex.MipMapLevels <- mipMapLevels - tex.Dimension <- TextureDimension.Texture2D - tex.Multisamples <- samples - tex.Count <- 1 - tex.Size <- V3i(size.X, size.Y, 1) - tex.Format <- format + let tex = new Texture(x, h, TextureDimension.TextureCube, mipMapLevels, 1, V3i(size, size, 1), None, format) + ResourceCounts.addTexture x tex.SizeInBytes + tex ) - member private x.UpdateTexture3D(tex : Texture, size : V3i, mipMapLevels : int, format : TextureFormat) = - let ifmt = TextureFormat.toSizedInternalFormat format - + member x.CreateTexture1DArray(size : int, count : int, mipMapLevels : int, format : TextureFormat) = using x.ResourceLock (fun _ -> - GL.BindTexture(TextureTarget.Texture3D, tex.Handle) - GL.Check "could not bind texture" - - x.AllocateTexture3D(TextureTarget.Texture3D, mipMapLevels, ifmt, size) - GL.Check "could not allocate texture" - - GL.BindTexture(TextureTarget.Texture3D, 0) - GL.Check "could not unbind texture" + if size > x.MaxTextureSize.X then + failf $"cannot create 1D array texture with size {size} (maximum is {x.MaxTextureSize.X})" - let sizeInBytes = ResourceCounts.texSizeInBytes(size, format, 1, mipMapLevels) - ResourceCounts.updateTexture tex.Context tex.SizeInBytes sizeInBytes - tex.SizeInBytes <- sizeInBytes + if count > x.MaxTextureArrayLayers then + failf $"cannot create 1D array texture with {count} layers (maximum is {x.MaxTextureArrayLayers})" - tex.MipMapLevels <- mipMapLevels - tex.Dimension <- TextureDimension.Texture3D - tex.Count <- 1 - tex.Multisamples <- 1 - tex.Size <- size - tex.Format <- format - ) - - member private x.UpdateTextureCube(tex : Texture, size : int, mipMapLevels : int, format : TextureFormat) = - let ifmt = TextureFormat.toSizedInternalFormat format + let h = GL.GenTexture() + GL.Check "could not create texture" - using x.ResourceLock (fun _ -> - GL.BindTexture(TextureTarget.TextureCubeMap, tex.Handle) + GL.BindTexture(TextureTarget.Texture1DArray, h) GL.Check "could not bind texture" - x.AllocateTexture2D(TextureTarget.TextureCubeMap, mipMapLevels, ifmt, V2i(size)) - GL.Check "could not allocate texture" + x.SetDefaultTextureParams(TextureTarget.Texture1DArray, format, mipMapLevels) + GL.Check "could not set default texture parameters" - GL.BindTexture(TextureTarget.TextureCubeMap, 0) - GL.Check "could not unbind texture" + GL.Dispatch.TexStorage2D(TextureTarget2d.Texture1DArray, mipMapLevels, TextureFormat.toSizedInternalFormat format, size, count) + GL.Check $"failed to allocate 1D array texture storage (format = {format}, size = {size}, count = {count}, levels = {mipMapLevels})" - let sizeInBytes = ResourceCounts.texSizeInBytes(V3i(size, size, 1), format, 1, mipMapLevels) * 6L - ResourceCounts.updateTexture tex.Context tex.SizeInBytes sizeInBytes - tex.SizeInBytes <- sizeInBytes + GL.BindTexture(TextureTarget.Texture1DArray, 0) + GL.Check "could not unbind texture" - tex.MipMapLevels <- mipMapLevels - tex.Dimension <- TextureDimension.TextureCube - tex.Size <- V3i(size, size, 1) - tex.Count <- 1 - tex.Format <- format + let tex = new Texture(x, h, TextureDimension.Texture1D, mipMapLevels, 1, V3i(size, 1, 1), Some count, format) + ResourceCounts.addTexture x tex.SizeInBytes + tex ) - member private x.UpdateTexture1DArray(tex : Texture, size : int, count : int, mipMapLevels : int, format : TextureFormat) = - let ifmt = TextureFormat.toSizedInternalFormat format - + member x.CreateTexture2DArray(size : V2i, count : int, mipMapLevels : int, format : TextureFormat, samples : int) = using x.ResourceLock (fun _ -> - GL.BindTexture(TextureTarget.Texture1DArray, tex.Handle) - GL.Check "could not bind texture" + if Vec.anyGreater size x.MaxTextureSize then + failf $"cannot create 2D array texture with size {size} (maximum is {x.MaxTextureSize})" - x.AllocateTexture2D(TextureTarget.Texture1DArray, mipMapLevels, ifmt, V2i(size, count)) - GL.Check "could not allocate texture" + if count > x.MaxTextureArrayLayers then + failf $"cannot create 2D array texture with {count} layers (maximum is {x.MaxTextureArrayLayers})" - GL.BindTexture(TextureTarget.Texture1DArray, 0) - GL.Check "could not unbind texture" - - let sizeInBytes = ResourceCounts.texSizeInBytes(V3i(size, 1, 1), format, 1, mipMapLevels) * (int64 count) - ResourceCounts.updateTexture tex.Context tex.SizeInBytes sizeInBytes - tex.SizeInBytes <- sizeInBytes - - tex.IsArray <- true - tex.MipMapLevels <- mipMapLevels - tex.Dimension <- TextureDimension.Texture1D - tex.Count <- count - tex.Multisamples <- 1 - tex.Size <- V3i(size, 1, 1) - tex.Format <- format - ) + let samples = + if samples <= 1 then 1 + else Image.validateSampleCount x ImageTarget.Texture2DMultisampleArray format samples - member private x.UpdateTexture2DArray(tex : Texture, size : V2i, count : int, mipMapLevels : int, format : TextureFormat, samples : int) = - using x.ResourceLock (fun _ -> let target = if samples = 1 then TextureTarget.Texture2DArray else TextureTarget.Texture2DMultisampleArray - let samples = - if samples > 1 then - let counts = x.GetFormatSamples(unbox target, format) - if counts.Contains samples then samples - else - let max = Set.maxElement counts - Log.warn "[GL] cannot create %A texture with %d samples (using %d instead)" format samples max - max - else - 1 - - let ifmt = TextureFormat.toSizedInternalFormat format + let h = GL.GenTexture() + GL.Check "could not create texture" - GL.BindTexture(target, tex.Handle) + GL.BindTexture(target, h) GL.Check "could not bind texture" + x.SetDefaultTextureParams(unbox target, format, mipMapLevels) + GL.Check "could not set default texture parameters" + + let ifmt = TextureFormat.toSizedInternalFormat format + if samples = 1 then - x.AllocateTexture3D(TextureTarget.Texture2DArray, mipMapLevels, ifmt, V3i(size.X, size.Y, count)) + GL.Dispatch.TexStorage3D(unbox target, mipMapLevels, ifmt, size.X, size.Y, count) + GL.Check $"failed to allocate 2D array texture storage (format = {format}, size = {size}, count = {count}, levels = {mipMapLevels})" else - x.AllocateTexture3DMultisample(TextureTarget.Texture2DMultisampleArray, samples, ifmt, V3i(size.X, size.Y, count), true) - - GL.Check "could not allocate texture" + GL.Dispatch.TexStorage3DMultisample(unbox target, samples, ifmt, size.X, size.Y, count, true) + GL.Check $"failed to allocate 2D array texture storage (format = {format}, size = {size}, count = {count}, samples = {samples})" GL.BindTexture(target, 0) GL.Check "could not unbind texture" - let sizeInBytes = ResourceCounts.texSizeInBytes(size.XYI, format, samples, mipMapLevels) * (int64 count) - ResourceCounts.updateTexture tex.Context tex.SizeInBytes sizeInBytes - tex.SizeInBytes <- sizeInBytes - - tex.MipMapLevels <- mipMapLevels - tex.Dimension <- TextureDimension.Texture2D - tex.IsArray <- true - tex.Count <- count - tex.Multisamples <- samples - tex.Size <- V3i(size.X, size.Y, 1) - tex.Format <- format + let tex = new Texture(x, h, TextureDimension.Texture2D, mipMapLevels, samples, size.XYI, Some count, format) + ResourceCounts.addTexture x tex.SizeInBytes + tex ) - member private x.UpdateTextureCubeArray(tex : Texture, size : int, count : int, mipMapLevels : int, format : TextureFormat) = + member x.CreateTextureCubeArray(size : int, count : int, mipMapLevels : int, format : TextureFormat) = using x.ResourceLock (fun _ -> - GL.BindTexture(TextureTarget.TextureCubeMapArray, tex.Handle) + if size > x.MaxTextureSizeCube then + failf $"cannot create cube array texture with size {size} (maximum is {x.MaxTextureSizeCube})" + + if count > x.MaxTextureArrayLayers then + failf $"cannot create cube array texture with {count} layers (maximum is {x.MaxTextureArrayLayers})" + + let h = GL.GenTexture() + GL.Check "could not create texture" + + GL.BindTexture(TextureTarget.TextureCubeMapArray, h) GL.Check "could not bind texture" - x.AllocateTexture3D(TextureTarget.TextureCubeMapArray, mipMapLevels, TextureFormat.toSizedInternalFormat format, V3i(size, size, count * 6)) - GL.Check "could not allocate texture" + GL.Dispatch.TexStorage3D(TextureTarget3d.TextureCubeMapArray, mipMapLevels, TextureFormat.toSizedInternalFormat format, size, size, count * 6) + GL.Check $"failed to allocate cube array texture storage (format = {format}, size = {size}, count = {count}, levels = {mipMapLevels})" GL.BindTexture(TextureTarget.TextureCubeMapArray, 0) GL.Check "could not unbind texture" - let sizeInBytes = ResourceCounts.texSizeInBytes(V3i(size, size, 1), format, 1, mipMapLevels) * 6L * (int64 count) - ResourceCounts.updateTexture tex.Context tex.SizeInBytes sizeInBytes - tex.SizeInBytes <- sizeInBytes - - tex.MipMapLevels <- mipMapLevels - tex.Dimension <- TextureDimension.TextureCube - tex.IsArray <- true - tex.Count <- count - tex.Size <- V3i(size, size, 1) - tex.Format <- format + let tex = new Texture(x, h, TextureDimension.TextureCube, mipMapLevels, 1, V3i(size, size, 1), Some count, format) + ResourceCounts.addTexture x tex.SizeInBytes + tex ) + // ================================================================================================================ + // CreateTextureView + // ================================================================================================================ + member x.CreateTextureView(orig : Texture, levels : Range1i, slices : Range1i, isArray : bool) = using x.ResourceLock (fun _ -> let handle = GL.GenTexture() diff --git a/src/Aardvark.Rendering.GL/Runtime/Compute.fs b/src/Aardvark.Rendering.GL/Runtime/Compute.fs index 902eaced..7025ab52 100644 --- a/src/Aardvark.Rendering.GL/Runtime/Compute.fs +++ b/src/Aardvark.Rendering.GL/Runtime/Compute.fs @@ -453,14 +453,14 @@ module internal ComputeTaskInternals = if GL.ARB_direct_state_access then s.ClearNamedBufferSubData( buffer.Handle, PixelInternalFormat.R32ui, - range.Offset, range.SizeInBytes, PixelFormat.Red, PixelType.UnsignedInt, + range.Offset, range.SizeInBytes, PixelFormat.RedInteger, PixelType.UnsignedInt, pValue ) else s.BindBuffer(BufferTarget.CopyWriteBuffer, buffer.Handle) s.ClearBufferSubData( BufferTarget.CopyWriteBuffer, PixelInternalFormat.R32ui, - range.Offset, range.SizeInBytes, PixelFormat.Red, PixelType.UnsignedInt, + range.Offset, range.SizeInBytes, PixelFormat.RedInteger, PixelType.UnsignedInt, pValue ) ) diff --git a/src/Aardvark.Rendering.GL/Runtime/GeometryPool.fs b/src/Aardvark.Rendering.GL/Runtime/GeometryPool.fs index beb6a9e0..c9ec0e4f 100644 --- a/src/Aardvark.Rendering.GL/Runtime/GeometryPool.fs +++ b/src/Aardvark.Rendering.GL/Runtime/GeometryPool.fs @@ -1234,6 +1234,8 @@ module GeometryPoolData = NativePtr.free mem ctx.Delete buffer if bounds then + culling.Dispose() + boxShader.Dispose() NativePtr.free bmem ctx.Delete bbuffer capacity <- 0 diff --git a/src/Aardvark.Rendering.GL/Runtime/Runtime.fs b/src/Aardvark.Rendering.GL/Runtime/Runtime.fs index 2195d688..2faf886e 100644 --- a/src/Aardvark.Rendering.GL/Runtime/Runtime.fs +++ b/src/Aardvark.Rendering.GL/Runtime/Runtime.fs @@ -51,10 +51,10 @@ type Runtime(debug : IDebugConfig) = // GL_CONTEXT_CORE_PROFILE_BIT 1 // GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 2 - let profileType = if driver.profileMask = 1 then " Core" elif driver.profileMask = 2 then " Compatibility" else "" + let profileType = if driver.profileMask = 1 then "Core" elif driver.profileMask = 2 then "Compatibility" else "" - Log.line "vendor: %A" driver.vendor - Log.line "renderer: %A" driver.renderer + Log.line "vendor: %s" driver.vendor + Log.line "renderer: %s" driver.renderer Log.line "version: OpenGL %A / GLSL %A %s" driver.version driver.glsl profileType let major = driver.version.Major @@ -401,7 +401,7 @@ type Runtime(debug : IDebugConfig) = member x.OnDispose = onDispose.Publish member x.AssembleModule (effect : Effect, signature : IFramebufferSignature, topology : IndexedGeometryMode) = - signature.Link(effect, Range1d(-1.0, 1.0), false, topology) + signature.Link(effect, Range1d(-1.0, 1.0), false, topology, not x.Context.SupportsLayeredEffects) member x.ResourceManager = manager diff --git a/src/Aardvark.Rendering.Vulkan/Core/Flags.fs b/src/Aardvark.Rendering.Vulkan/Core/Flags.fs index edec9fdb..3f3afde3 100644 --- a/src/Aardvark.Rendering.Vulkan/Core/Flags.fs +++ b/src/Aardvark.Rendering.Vulkan/Core/Flags.fs @@ -372,4 +372,11 @@ module VkExternalMemoryHandleTypeFlags = if isWindows then VkExternalMemoryHandleTypeFlags.OpaqueWin32Bit else - VkExternalMemoryHandleTypeFlags.OpaqueFdBit \ No newline at end of file + VkExternalMemoryHandleTypeFlags.OpaqueFdBit + +[] +module VkExternalMemoryPropertiesExtensions = + open Vulkan11 + + type VkExternalMemoryProperties with + member inline x.IsExportable = x.externalMemoryFeatures.HasFlag VkExternalMemoryFeatureFlags.ExportableBit \ No newline at end of file diff --git a/src/Aardvark.Rendering.Vulkan/Core/Platform.fs b/src/Aardvark.Rendering.Vulkan/Core/Platform.fs index 8f34a2f0..8f9cebfd 100644 --- a/src/Aardvark.Rendering.Vulkan/Core/Platform.fs +++ b/src/Aardvark.Rendering.Vulkan/Core/Platform.fs @@ -660,31 +660,34 @@ and PhysicalDevice internal(instance : Instance, handle : VkPhysicalDevice) = | VkImageTiling.Linear -> formatProperties.[fmt].linearTilingFeatures | _ -> formatProperties.[fmt].optimalTilingFeatures - member private x.GetImageProperties(format : VkFormat, typ : VkImageType, tiling : VkImageTiling, usage : VkImageUsageFlags, flags : VkImageCreateFlags) = - let key = (format, typ, tiling, usage, flags) + member internal x.GetImageProperties(format : VkFormat, typ : VkImageType, tiling : VkImageTiling, usage : VkImageUsageFlags, + flags : VkImageCreateFlags, external : VkExternalMemoryHandleTypeFlags) = + let key = (format, typ, tiling, usage, flags, external) imageFormatProperties.GetOrCreate(key, fun _ -> native { let! pExternalImageFormatInfo = - VkPhysicalDeviceExternalImageFormatInfo VkExternalMemoryHandleTypeFlags.OpaqueBit + VkPhysicalDeviceExternalImageFormatInfo external let! pImageFormatInfo = - VkPhysicalDeviceImageFormatInfo2( - NativePtr.toNativeInt pExternalImageFormatInfo, - format, typ, tiling, usage, flags - ) + let pNext = + if external = VkExternalMemoryHandleTypeFlags.None then 0n + else NativePtr.toNativeInt pExternalImageFormatInfo + + VkPhysicalDeviceImageFormatInfo2(pNext, format, typ, tiling, usage, flags) let! pExternalImageFormatProperties = VkExternalImageFormatProperties.Empty let! pImageFormatProperties = - VkImageFormatProperties2( - NativePtr.toNativeInt pExternalImageFormatProperties, - VkImageFormatProperties.Empty - ) + let pNext = + if external = VkExternalMemoryHandleTypeFlags.None then 0n + else NativePtr.toNativeInt pExternalImageFormatProperties + + VkImageFormatProperties2(pNext, VkImageFormatProperties.Empty) VkRaw.vkGetPhysicalDeviceImageFormatProperties2(x.Handle, pImageFormatInfo, pImageFormatProperties) - |> check "could not query image format properties" + |> check $"could not query image format properties (format = {format}, type = {typ}, tiling = {tiling}, usage = {usage}, flags = {flags}, external = {external})" let imageFormatProperties = (!!pImageFormatProperties).imageFormatProperties let externalMemoryProperties = (!!pExternalImageFormatProperties).externalMemoryProperties @@ -693,11 +696,11 @@ and PhysicalDevice internal(instance : Instance, handle : VkPhysicalDevice) = ) member x.GetImageFormatProperties(format : VkFormat, typ : VkImageType, tiling : VkImageTiling, usage : VkImageUsageFlags, flags : VkImageCreateFlags) = - fst <| x.GetImageProperties(format, typ, tiling, usage, flags) + fst <| x.GetImageProperties(format, typ, tiling, usage, flags, VkExternalMemoryHandleTypeFlags.None) member x.GetImageExportable(format : VkFormat, typ : VkImageType, tiling : VkImageTiling, usage : VkImageUsageFlags, flags : VkImageCreateFlags) = - let _, externalMemoryProperties = x.GetImageProperties(format, typ, tiling, usage, flags) - externalMemoryProperties.externalMemoryFeatures.HasFlag VkExternalMemoryFeatureFlags.ExportableBit + let _, externalMemoryProperties = x.GetImageProperties(format, typ, tiling, usage, flags, VkExternalMemoryHandleTypeFlags.OpaqueBit) + externalMemoryProperties.IsExportable member x.GetBufferFormatFeatures(fmt : VkFormat) = formatProperties.[fmt].bufferFeatures @@ -721,7 +724,7 @@ and PhysicalDevice internal(instance : Instance, handle : VkPhysicalDevice) = member x.GetBufferExportable(flags : VkBufferCreateFlags, usage : VkBufferUsageFlags) = let properties = x.GetExternalBufferProperties(flags, usage) - properties.externalMemoryProperties.externalMemoryFeatures.HasFlag VkExternalMemoryFeatureFlags.ExportableBit + properties.externalMemoryProperties.IsExportable member x.AvailableLayers = availableLayers member x.GlobalExtensions : ExtensionInfo[] = globalExtensions diff --git a/src/Aardvark.Rendering.Vulkan/Core/Utilities.fs b/src/Aardvark.Rendering.Vulkan/Core/Utilities.fs index 614cc0ee..5c7892ab 100644 --- a/src/Aardvark.Rendering.Vulkan/Core/Utilities.fs +++ b/src/Aardvark.Rendering.Vulkan/Core/Utilities.fs @@ -10,6 +10,15 @@ open Aardvark.Rendering #nowarn "9" +type VulkanException(error : VkResult, message : string, [] innerException : Exception) = + inherit Exception(message, innerException) + + new(message : string, [] innerException : Exception) = + VulkanException(VkResult.ErrorUnknown, message, innerException) + + member x.Error = error + override x.Message = $"{message} (Error: {error})" + [] module private Utilities = @@ -130,21 +139,26 @@ module private Utilities = } let check (str : string) (err : VkResult) = - if err <> VkResult.Success then - Log.error "[Vulkan] %s (%A)" str err - failwithf "[Vulkan] %s (%A)" str err + if err <> VkResult.Success then + let msg = + if String.IsNullOrEmpty str then "An error occurred" + else string (Char.ToUpper str.[0]) + str.Substring(1) + + Report.Error $"[Vulkan] {msg} (Error: {err})" + raise <| VulkanException(err, msg) let checkf (fmt : Printf.StringFormat<'a, VkResult -> unit>) = - Printf.kprintf (fun (str : string) (res : VkResult) -> - if res <> VkResult.Success then - Log.error "[Vulkan] %s (%A)" str res - failwithf "[Vulkan] %s (%A)" str res - ) fmt + Printf.kprintf check fmt let inline failf fmt = - Printf.kprintf (fun str -> - Log.error $"[Vulkan] {str}" - failwith ("[Vulkan] " + str) + Printf.kprintf (fun str -> + let str = + if String.IsNullOrEmpty str then "An error occurred" + else string (Char.ToUpper str.[0]) + str.Substring(1) + + let msg = $"[Vulkan] {str}" + Report.Error msg + failwith msg ) fmt module Map = diff --git a/src/Aardvark.Rendering.Vulkan/Resources/Image/Image.fs b/src/Aardvark.Rendering.Vulkan/Resources/Image/Image.fs index 383cbde1..42865620 100644 --- a/src/Aardvark.Rendering.Vulkan/Resources/Image/Image.fs +++ b/src/Aardvark.Rendering.Vulkan/Resources/Image/Image.fs @@ -103,6 +103,7 @@ module VkImageAspectFlags = type ImageExportMode = | None | Export of preferArray: bool + member inline x.Enabled = x <> None type Image = class @@ -1059,8 +1060,20 @@ module Image = let usage = usage |> VkImageUsageFlags.filterSupported features - let properties = - device.PhysicalDevice.GetImageFormatProperties(fmt, typ, VkImageTiling.Optimal, usage, flags) + let properties, externalMemoryProperties = + let external = + if export.Enabled then + if not <| device.IsExtensionEnabled ExternalMemory.Extension then + failf $"Cannot export image memory because {ExternalMemory.Extension} is not supported" + + VkExternalMemoryHandleTypeFlags.OpaqueBit + else + VkExternalMemoryHandleTypeFlags.None + + device.PhysicalDevice.GetImageProperties(fmt, typ, VkImageTiling.Optimal, usage, flags, external) + + if export.Enabled && not externalMemoryProperties.IsExportable then + failf $"Cannot export {fmt} image with usage {usage}" let maxExtent = V3l.OfExtent properties.maxExtent @@ -1085,19 +1098,11 @@ module Image = let! pExternalMemoryInfo = VkExternalMemoryImageCreateInfo VkExternalMemoryHandleTypeFlags.OpaqueBit - let pNext = - if export <> ImageExportMode.None then - if device.IsExtensionEnabled ExternalMemory.Extension then - if device.PhysicalDevice.GetImageExportable(fmt, typ, VkImageTiling.Optimal, usage, flags) then - NativePtr.toNativeInt pExternalMemoryInfo - else - failf $"Cannot export {fmt} image with usage {usage}" - else - failf $"Cannot export image memory because {ExternalMemory.Extension} is not supported" - else - 0n - let! pInfo = + let pNext = + if export.Enabled then NativePtr.toNativeInt pExternalMemoryInfo + else 0n + VkImageCreateInfo( pNext, flags, diff --git a/src/Aardvark.Rendering.Vulkan/Swapchain/SwapchainDescription.fs b/src/Aardvark.Rendering.Vulkan/Swapchain/SwapchainDescription.fs index c9cdd776..5d2216ea 100644 --- a/src/Aardvark.Rendering.Vulkan/Swapchain/SwapchainDescription.fs +++ b/src/Aardvark.Rendering.Vulkan/Swapchain/SwapchainDescription.fs @@ -135,7 +135,8 @@ type GraphicsMode(format : Col.Format, bits : int, depthBits : int, stencilBits modes |> Seq.maxBy presentModeScore override x.ChooseBufferCount(min, max) = - clamp min max buffers + if max = 0 then Fun.Max(min, buffers) + else clamp min max buffers type SwapchainDescription = { diff --git a/src/Aardvark.Rendering/Effects/Interop/FShade.fs b/src/Aardvark.Rendering/Effects/Interop/FShade.fs index 113d5321..2a9f128d 100644 --- a/src/Aardvark.Rendering/Effects/Interop/FShade.fs +++ b/src/Aardvark.Rendering/Effects/Interop/FShade.fs @@ -437,8 +437,7 @@ module FShadeInterop = flipHandedness = flip } - - member x.Link(effect : Effect, deviceCount : int, depthRange : Range1d, flip : bool, top : IndexedGeometryMode) = + member x.Link(effect : Effect, deviceCount : int, depthRange : Range1d, flip : bool, top : IndexedGeometryMode, useCustomSemantic : bool) = let outputs = x.ColorAttachments |> Map.toList @@ -454,26 +453,38 @@ module FShadeInterop = if deviceCount > 1 then if x.LayerCount > 1 then + let semantic = Intrinsics.Layer + let customSemantic = if useCustomSemantic then "GeometryInvocationId" else semantic + effect - // TODO: other topologies???? - |> Effect.toLayeredEffect x.LayerCount (x.PerLayerUniforms |> Seq.map (fun n -> n, n) |> Map.ofSeq) top - |> withDeviceIndex deviceCount - |> Effect.toModule config + // TODO: other topologies???? + |> Effect.toLayered semantic customSemantic x.LayerCount (x.PerLayerUniforms |> Seq.map (fun n -> n, n) |> Map.ofSeq) top + |> withDeviceIndex deviceCount + |> Effect.toModule config else + let semantic = Intrinsics.ViewportIndex + let customSemantic = if useCustomSemantic then "GeometryInvocationId" else semantic + effect - // TODO: other topologies???? - |> Effect.toMultiViewportEffect deviceCount Map.empty top - |> withDeviceIndex deviceCount - |> Effect.toModule config + // TODO: other topologies???? + |> Effect.toLayered semantic customSemantic deviceCount Map.empty top + |> withDeviceIndex deviceCount + |> Effect.toModule config else if x.LayerCount > 1 then + let semantic = Intrinsics.Layer + let customSemantic = if useCustomSemantic then "GeometryInvocationId" else semantic + effect - // TODO: other topologies???? - |> Effect.toLayeredEffect x.LayerCount (x.PerLayerUniforms |> Seq.map (fun n -> n, n) |> Map.ofSeq) top - |> Effect.toModule config + // TODO: other topologies???? + |> Effect.toLayered semantic customSemantic x.LayerCount (x.PerLayerUniforms |> Seq.map (fun n -> n, n) |> Map.ofSeq) top + |> Effect.toModule config else effect |> Effect.toModule config + member x.Link(effect : Effect, deviceCount : int, depthRange : Range1d, flip : bool, top : IndexedGeometryMode) = + x.Link(effect, deviceCount, depthRange, flip, top, false) + type IFramebufferSignature with member x.Layout : FramebufferLayout = { @@ -484,8 +495,14 @@ module FShadeInterop = PerLayerUniforms = x.PerLayerUniforms } - member x.EffectConfig(depthRange : Range1d, flip : bool) = x.Layout.EffectConfig(depthRange, flip) - member x.Link(effect : Effect, depthRange : Range1d, flip : bool, top : IndexedGeometryMode) = x.Layout.Link(effect, x.Runtime.DeviceCount, depthRange, flip, top) + member x.EffectConfig(depthRange : Range1d, flip : bool) = + x.Layout.EffectConfig(depthRange, flip) + + member x.Link(effect : Effect, depthRange : Range1d, flip : bool, top : IndexedGeometryMode) = + x.Layout.Link(effect, x.Runtime.DeviceCount, depthRange, flip, top, false) + + member x.Link(effect : Effect, depthRange : Range1d, flip : bool, top : IndexedGeometryMode, useCustomSemantic : bool) = + x.Layout.Link(effect, x.Runtime.DeviceCount, depthRange, flip, top, useCustomSemantic) type FShadeSurface private(effect : FShadeEffect) = diff --git a/src/Aardvark.Rendering/Pipeline/Camera.fs b/src/Aardvark.Rendering/Pipeline/Camera.fs index 4d3890de..f97cca10 100644 --- a/src/Aardvark.Rendering/Pipeline/Camera.fs +++ b/src/Aardvark.Rendering/Pipeline/Camera.fs @@ -259,30 +259,15 @@ module Frustum = (r - l) / (t - b) let withAspect (newAspect : float) ( { left = l; right = r; top = t; bottom = b } as f ) = - let factor = newAspect / aspect f - { f with right = factor * r; left = factor * l } + let factor = aspect f / newAspect + { f with top = factor * t; bottom = factor * b } let withHorizontalFieldOfViewInDegrees (angleInDegrees : float) (frustum : Frustum) = if frustum.isOrtho then frustum else - let lt = atan2 frustum.left frustum.near - let rt = atan2 frustum.right frustum.near - - let total = rt - lt - let f = angleInDegrees / total - - let ll = lt * f - let rr = rt * f - - let l = tan ll * frustum.near - let r = tan rr * frustum.near - - - let t = tan (atan2 frustum.top frustum.near * f) * frustum.near - let b = tan (atan2 frustum.bottom frustum.near * f) * frustum.near - - { frustum with left = l; right = r; top = t; bottom = b } + let aspect = aspect frustum + perspective angleInDegrees frustum.near frustum.far aspect let horizontalFieldOfViewInDegrees { left = l; right = r; near = near } = let l,r = atan2 l near, atan2 r near diff --git a/src/Aardvark.Rendering/RenderTasks/RenderTask.fs b/src/Aardvark.Rendering/RenderTasks/RenderTask.fs index 4cb6a5a3..54d92296 100644 --- a/src/Aardvark.Rendering/RenderTasks/RenderTask.fs +++ b/src/Aardvark.Rendering/RenderTasks/RenderTask.fs @@ -96,8 +96,8 @@ module RenderTask = /// Runs a render task for the given adaptive size and returns a map containing the textures specified as output. /// The resulting framebuffer is cleared according to the given adaptive clear values before the render task is executed. let renderSemanticsWithAdaptiveClear (output : Set) (size : aval) (clearValues : aval) (task : IRenderTask) = - let runtime = task.Runtime.Value - let signature = task.FramebufferSignature.Value + let runtime = task.GetRuntime() + let signature = task.GetFramebufferSignature() let attachments = signature.GetSemantics() let fbo = runtime.CreateFramebuffer(signature, size, Set.difference attachments output) @@ -188,8 +188,8 @@ module RenderTask = /// The resulting framebuffers are cleared according to the given clear values before the render tasks are executed. let renderSemanticsCubeMipWithClear (output : Set) (size : aval) (clearValues : CubeMap) (tasks : CubeMap) = let task = tasks.Data.[0] - let runtime = task.Runtime.Value - let signature = task.FramebufferSignature.Value + let runtime = task.GetRuntime() + let signature = task.GetFramebufferSignature() let fbo = runtime.CreateFramebufferCube(signature, size, tasks.Levels) let res = tasks.RenderTo(fbo, clearValues) diff --git a/src/Aardvark.Rendering/RenderTasks/RenderToExtensions.fs b/src/Aardvark.Rendering/RenderTasks/RenderToExtensions.fs index 873e3019..0d467ee8 100644 --- a/src/Aardvark.Rendering/RenderTasks/RenderToExtensions.fs +++ b/src/Aardvark.Rendering/RenderTasks/RenderToExtensions.fs @@ -113,8 +113,8 @@ type RenderToExtensions private() = /// Renders the given task to the given framebuffer, after clearing it according to the given adaptive clear values. [] static member RenderTo(this : IRenderTask, output : IAdaptiveResource, clearValues : aval) = - let runtime = this.Runtime.Value - let signature = this.FramebufferSignature.Value + let runtime = this.GetRuntime() + let signature = this.GetFramebufferSignature() let task = let mutable clear = Unchecked.defaultof @@ -155,8 +155,8 @@ type RenderToExtensions private() = [] static member RenderTo(this : CubeMap<#IRenderTask>, output : IAdaptiveResource>, clearValues : CubeMap>) = let task = this.Data.[0] - let runtime = task.Runtime.Value - let signature = task.FramebufferSignature.Value + let runtime = task.GetRuntime() + let signature = task.GetFramebufferSignature() let compiled = let cache = Dict, IRenderTask>() diff --git a/src/Aardvark.Rendering/Resources/Textures/Formats.fs b/src/Aardvark.Rendering/Resources/Textures/Formats.fs index 7b1e25c6..0586902c 100644 --- a/src/Aardvark.Rendering/Resources/Textures/Formats.fs +++ b/src/Aardvark.Rendering/Resources/Textures/Formats.fs @@ -546,7 +546,7 @@ module TextureFormat = LookupTable.lookupTable [ TextureFormat.Bgr8, 24 TextureFormat.Bgra8, 32 - TextureFormat.R3G3B2, 6 + TextureFormat.R3G3B2, 8 TextureFormat.Rgb4, 12 TextureFormat.Rgb5, 15 TextureFormat.Rgb8, 24 diff --git a/src/Aardvark.Rendering/Runtime/Runtime.fs b/src/Aardvark.Rendering/Runtime/Runtime.fs index c85d0832..15bc9e97 100644 --- a/src/Aardvark.Rendering/Runtime/Runtime.fs +++ b/src/Aardvark.Rendering/Runtime/Runtime.fs @@ -123,3 +123,15 @@ type RenderTaskRunExtensions() = [] static member Run(t : IRenderTask, token : RenderToken, fbo : OutputDescription) = t.Run(AdaptiveToken.Top, token, fbo) + + [] + static member inline GetRuntime(t : IRenderTask) = + match t.Runtime with + | Some r -> r + | _ -> raise <| InvalidOperationException("Render task does not have a runtime.") + + [] + static member inline GetFramebufferSignature(t : IRenderTask) = + match t.FramebufferSignature with + | Some s -> s + | _ -> raise <| InvalidOperationException("Render task does not have a framebuffer signature.") \ No newline at end of file diff --git a/src/Application/Aardvark.Application.Slim.GL/Application.fs b/src/Application/Aardvark.Application.Slim.GL/Application.fs index d102431e..9d7e68fd 100644 --- a/src/Application/Aardvark.Application.Slim.GL/Application.fs +++ b/src/Application/Aardvark.Application.Slim.GL/Application.fs @@ -28,7 +28,7 @@ module private OpenGL = open FSharp.Data.Adaptive let mutable version = System.Version(3,3) - let mutable useNoError = false + let mutable supportsNoError = false let private tryCreateOffscreenWindow (version : Version) (useNoError : bool) (glfw : Glfw) = glfw.DefaultWindowHints() @@ -50,13 +50,13 @@ module private OpenGL = else glfw.DestroyWindow w true - - let supportsNoError (version : Version) (glfw : Glfw) = + + let queryNoErrorSupport (version : Version) (glfw : Glfw) = if tryCreateOffscreenWindow version true glfw then true else let error, _ = glfw.GetError() - Report.Line(2, "OpenGL does not support KHR_no_error: {0}", error) + Report.Line(2, $"OpenGL does not support KHR_no_error ({error})") false let initVersion (glfw : Glfw) = @@ -90,7 +90,7 @@ module private OpenGL = match best with | Some b -> version <- b - useNoError <- glfw |> supportsNoError b + supportsNoError <- glfw |> queryNoErrorSupport b | None -> failwith "no compatible OpenGL version found" type MyWindowInfo(win : nativeptr) = @@ -292,7 +292,10 @@ module private OpenGL = () } - let interop = + let interop (debug : DebugConfig) = + let disableErrorChecks = + debug.ErrorFlagCheck = ErrorFlagCheck.Disabled + { new IWindowInterop with override __.Boot(glfw) = initVersion glfw @@ -307,7 +310,6 @@ module private OpenGL = glfw.WindowHint(WindowHintInt.DepthBits, 24) glfw.WindowHint(WindowHintInt.StencilBits, 8) - let m = glfw.GetPrimaryMonitor() let mode = glfw.GetVideoMode(m) |> NativePtr.read glfw.WindowHint(WindowHintInt.RedBits, 8) @@ -320,7 +322,7 @@ module private OpenGL = glfw.WindowHint(WindowHintBool.OpenGLForwardCompat, true) glfw.WindowHint(WindowHintBool.DoubleBuffer, true) glfw.WindowHint(WindowHintBool.OpenGLDebugContext, false) - if useNoError then glfw.WindowHint(WindowHintBool.ContextNoError, true) + glfw.WindowHint(WindowHintBool.ContextNoError, disableErrorChecks && supportsNoError) glfw.WindowHint(WindowHintBool.SrgbCapable, false) if RuntimeInformation.IsOSPlatform(OSPlatform.OSX) then glfw.WindowHint(WindowHintBool.CocoaRetinaFramebuffer, cfg.physicalSize) @@ -330,7 +332,7 @@ module private OpenGL = } type OpenGlApplication private (runtime : Runtime, shaderCachePath : Option, hideCocoaMenuBar : bool) as this = - inherit Application(runtime, OpenGL.interop, hideCocoaMenuBar) + inherit Application(runtime, OpenGL.interop runtime.DebugConfig, hideCocoaMenuBar) let createContext() = let w = this.Instance.CreateWindow WindowConfig.Default diff --git a/src/Tests/Aardvark.Rendering.Tests/Aardvark.Rendering.Tests.fsproj b/src/Tests/Aardvark.Rendering.Tests/Aardvark.Rendering.Tests.fsproj index 6eaffeee..d8c58255 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Aardvark.Rendering.Tests.fsproj +++ b/src/Tests/Aardvark.Rendering.Tests/Aardvark.Rendering.Tests.fsproj @@ -56,6 +56,8 @@ + + @@ -68,6 +70,7 @@ + diff --git a/src/Tests/Aardvark.Rendering.Tests/Program.fs b/src/Tests/Aardvark.Rendering.Tests/Program.fs index ad32697f..bbf20024 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Program.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Program.fs @@ -1,281 +1,75 @@ open Aardvark.Rendering.Tests -open Aardvark.Rendering.GL.Tests +open Aardvark.Application +open Expecto -open System -open Aardvark.Base - -open FSharp.Data.Adaptive -open Aardvark.Rendering -open Aardvark.Rendering.GL -open OpenTK.Graphics.OpenGL4 -open Aardvark.Application.Slim - -open BenchmarkDotNet.Running; -open BenchmarkDotNet.Configs -open BenchmarkDotNet.Jobs -open BenchmarkDotNet.Toolchains +[] +let main argv = -let testCompile() = - use runtime = new Runtime(DebugLevel.None) - let ctx = new Context(runtime, fun () -> ContextHandleOpenTK.create runtime.DebugConfig) - runtime.Initialize(ctx) + let backendTests backend = + let bufferTests = + testBackend backend "Buffers" [ + Buffer.BufferCopy.tests + Buffer.BufferUpload.tests + Buffer.BufferDownload.tests + Buffer.AttributeBuffer.tests + ] - let signature = - runtime.CreateFramebufferSignature( - [ - DefaultSemantic.Colors, TextureFormat.Rgba8 - DefaultSemantic.DepthStencil, TextureFormat.Depth24Stencil8 + let textureTests = + testBackend backend "Textures" [ + Texture.TextureUpload.tests + Texture.TextureDownload.tests + Texture.TextureCreate.tests + Texture.TextureCopy.tests + Texture.TextureClear.tests ] - ) - let callInfo = - DrawCallInfo( - FaceVertexCount = 6, - InstanceCount = 1 - ) + let renderingTests = + testBackend backend "Rendering" [ + Rendering.Culling.tests + Rendering.Blending.tests + Rendering.ColorMasks.tests + Rendering.RenderTasks.tests + Rendering.FramebufferSignature.tests + Rendering.IntegerAttachments.tests + Rendering.Samplers.tests + Rendering.Uniforms.tests + ] - let surface = - runtime.PrepareEffect( - signature, - [ - DefaultSurfaces.constantColor C4f.Red |> toEffect + let computeTests = + testBackend backend "Compute" [ + Compute.ComputeImages.tests + Compute.ComputeBuffers.tests + Compute.ComputePrimitives.tests + Compute.ComputeSorting.tests + Compute.ComputeJpeg.tests + Compute.MutableInputBinding.tests ] - ) - let uniforms (t : V3d) = - UniformProvider.ofList [ - Symbol.Create "ModelTrafo", AVal.constant (Trafo3d.Translation t) :> IAdaptiveValue - Symbol.Create "ViewProjTrafo", AVal.constant Trafo3d.Identity :> IAdaptiveValue + testList $"Tests ({backend})" [ + bufferTests + textureTests + renderingTests + computeTests ] - let attributes = - AttributeProvider.ofList [ - DefaultSemantic.Positions, [| V3f(-1,-1,0); V3f(1,-1,0); V3f(1,1,0); V3f(1,-1,0); V3f(1,1,0); V3f(-1,1,0) |] :> Array + let otherTests = + testList "Other tests" [ + ``SceneGraph Tests``.tests + ``CompactSet Tests``.tests + ``AdaptiveResource Tests``.tests + ``Camera Tests``.tests ] - let prototype = - let ro = RenderObject() - ro.DrawCalls <- Direct(AVal.constant [ callInfo ]) - ro.Surface <- Surface.Backend (surface :> ISurface) - ro.RasterizerState <- { RasterizerState.Default with FrontFacing = AVal.constant WindingOrder.CounterClockwise } - ro.VertexAttributes <- attributes - ro.Uniforms <- uniforms V3d.Zero - ro - - let framebuffer = runtime.CreateFramebuffer(signature, AVal.constant(V2i(1024, 1024))) - framebuffer.Acquire() - - let objects = - Array.init (1 <<< 20) (fun i -> - let ro = RenderObject.Clone prototype - ro.Uniforms <- uniforms (V3d(i,0,0)) - ro :> IRenderObject - ) - - let fbo = framebuffer.GetValue() - - //let set = ASet.ofArray (Array.take (1 <<< 16) objects) - //let commands = AList.ofArray (Array.take (1 <<< 16) objects |> Array.map RenderCommand.Render) - -// Log.line "starting" -// while true do -// //let task = runtime.CompileRender(signature, BackendConfiguration.Default, set) -// let task = runtime.Compile(signature, commands) -// task.Run(RenderToken.Empty, fbo) -// //task.Dispose() - - - let log = @"C:\Users\Schorsch\Desktop\perfOld.csv" - - for cnt in 1000 .. 1000 .. 100000 do - let set = ASet.ofArray (Array.take cnt objects) - //let commands = AList.ofArray (Array.take cnt objects |> Array.map RenderCommand.Render) - - Log.startTimed "compile %d" cnt - let sw = System.Diagnostics.Stopwatch.StartNew() - //let task = runtime.Compile(signature, commands) - let task = runtime.CompileRender(signature, set) - task.Run(RenderToken.Empty, fbo) - sw.Stop() - Log.stop() - task.Dispose() - System.IO.File.AppendAllLines(log, [sprintf "%d:%d" cnt sw.MicroTime.TotalNanoseconds]) - - - () - -let clearTexture(runtime : Aardvark.Rendering.GL.Runtime, texture : IBackendTexture, color : C4f, level : int) = - - using runtime.Context.ResourceLock (fun _ -> - - // create temporary fbo - let fbo = GL.GenFramebuffer() - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, fbo) - GL.FramebufferTexture(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0, unbox texture.Handle, level) - GL.Check "[GL] ClearTexture: could not create FramebufferTexture" - - // perform clear - GL.ClearColor(color.R, color.G, color.B, color.A) - GL.Clear(ClearBufferMask.ColorBufferBit) - GL.Check "[GL] ClearTexture: could not clear texture" - - // unbind and delete of fbo - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0) - GL.Check "[GL] ClearTexture: could not unbind Framebuffer" - GL.DeleteFramebuffer(fbo) - GL.Check "[GL] ClearTexture: could not delete FramebufferTexture" - ) - -let testDownloadSlice() = - let app = new OpenGlApplication(false, true) - let runtime = app.Runtime - let texRt = runtime :> ITextureRuntime - let tex = texRt.CreateTexture2DArray(V2i(222,333), TextureFormat.Rgba8, 1, 8, 5) - let path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) - - clearTexture(runtime, tex, C4f.Red, 0) - runtime.Download(tex, 0, 0).Save(Path.combine [path; "slice0.bmp"]) - - clearTexture(runtime, tex, C4f.Blue, 0) - runtime.Download(tex, 0, 1).Save(Path.combine [path; "slice1.bmp"]) - - () - -let testTextureCubeArray() = - let app = new OpenGlApplication(false, DebugLevel.Normal) - let runtime = app.Runtime - let texRt = runtime :> ITextureRuntime - - let cta = texRt.CreateTextureCubeArray(128, TextureFormat.Rgba8, 1, 4) - - let cube0View = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(0,5), false) // create TextureCube view of [0] - let cube1View = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(6,11), false) // create TextureCube view of [1] - let cube0Face0View = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(0,0), false) // create Texture2d view of cube[0].face[0] - let cube0Face1View = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(1,1), false) // create Texture2d view of cube[0].face[1] - let cube1Face2View = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(8,8), false) // create Texture2d view of cube[1].face[2] - let cube2Face0To2View = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(12,14), true) // create Texture2dArray of range - let cube2Face0To5View = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(12,17), true) // create Texture2dArray of range spanning cube - let cube0to1FacesView = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(0,11), true) // create Texture2dArray of complete cube 0 & 1 - let cubeArrayFacesView = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(0,23), true) // create Texture2dArray of complete TextureCubeArray - //let cube0Cube1ArrayView = texRt.CreateTextureView(cta, Range1i(0,0), Range1i(0,11), true) // API does not allow to create TextureCubeArray sub-range - - texRt.DeleteTexture(cta) - texRt.DeleteTexture(cube0View) - texRt.DeleteTexture(cube1View) - texRt.DeleteTexture(cube0Face0View) - texRt.DeleteTexture(cube0Face1View) - texRt.DeleteTexture(cube1Face2View) - texRt.DeleteTexture(cube2Face0To2View) - texRt.DeleteTexture(cube2Face0To5View) - texRt.DeleteTexture(cube0to1FacesView) - texRt.DeleteTexture(cubeArrayFacesView) - - -let testCopySlice() = - let app = new OpenGlApplication(false, DebugLevel.Normal) - let runtime = app.Runtime - let texRt = runtime :> ITextureRuntime - let texSrc = texRt.CreateTexture2DArray(V2i(222,333), TextureFormat.Rgba8, 1, 1, 2) - let texDst = texRt.CreateTexture2DArray(V2i(222,333), TextureFormat.Rgba8, 1, 1, 2) - - clearTexture(runtime, texSrc, C4f.Red, 0) - clearTexture(runtime, texDst, C4f.Blue, 0) - - // NOTE: can only be test when testDownloadSlice is fixed - runtime.Download(texSrc, 0, 0).Save("C:\\Debug\\testCopySlice_src_slice0.bmp") // -> should be Red - runtime.Download(texSrc, 0, 1).Save("C:\\Debug\\testCopySlice_src_slice1.bmp") // -> should be Red - - // alternative: - let texSrcTile0View = texRt.CreateTextureView(texSrc, Range1i(0,0), Range1i(0,0), false) - runtime.Download(texSrcTile0View).Save("C:\\Debug\\testCopySlice_src_slice0_asView.bmp") - let texSrcTile1View = texRt.CreateTextureView(texSrc, Range1i(0,0), Range1i(1,1), false) - runtime.Download(texSrcTile1View).Save("C:\\Debug\\testCopySlice_src_slice1_asView.bmp") - - // copy first slice - runtime.Copy(texSrc, 0, 0, texDst, 0, 0, 1, 1) - - // NOTE: can only be test when testDownloadSlice is fixed - runtime.Download(texDst, 0, 0).Save("C:\\Debug\\testCopySlice_dst_slice0.bmp") // -> should be Red - runtime.Download(texDst, 0, 1).Save("C:\\Debug\\testCopySlice_dst_slice1.bmp") // -> should be Blue - - // alternative: - let texDstTile0View = texRt.CreateTextureView(texDst, Range1i(0,0), Range1i(0,0), false) - runtime.Download(texDstTile0View).Save("C:\\Debug\\testCopySlice_dst_slice0_asView.bmp") // -> should be Red - let texDstTile1View = texRt.CreateTextureView(texDst, Range1i(0,0), Range1i(1,1), false) - runtime.Download(texDstTile1View).Save("C:\\Debug\\testCopySlice_dst_slice1_asView.bmp") // -> should be Blue - - () - -module CSTest = - - module Shader = - open FShade - - [] - let write (img : Image2d) = - compute { - let id = getGlobalId().XY - let s = img.Size - if id.X < s.X && id.Y < s.Y then - img.[id] <- V4d(V2d id / V2d s, 1.0, 1.0) - } - - let run() = - - let app = new OpenGlApplication() - let runtime = app.Runtime :> IRuntime - - let dst = runtime.CreateTexture2D(V2i(1024, 1024), TextureFormat.Rgba8, 1, 1) - - let sh = runtime.CreateComputeShader Shader.write - let ip = runtime.CreateInputBinding sh - ip.["img"] <- dst.[TextureAspect.Color, 0, 0] - ip.Flush() - - runtime.Run [ - ComputeCommand.Bind sh - ComputeCommand.SetInput ip - ComputeCommand.Dispatch (dst.Size.XY / 8) + let allTests = + testList "Tests" [ + otherTests + backendTests Backend.GL + backendTests Backend.Vulkan ] - let img = PixImage(Col.Format.RGBA, dst.Size.XY) - dst.Download(img) - img.SaveImageSharp @"C:\Users\Schorsch\Desktop\bla.png" - - - -[] -let main argv = - //Aardvark.Init() - //CSTest.run() - - //let cfg = - // let job = Job.Default.WithToolchain(InProcess.Emit.InProcessEmitToolchain.Instance) - // ManualConfig.Create(DefaultConfig.Instance).AddJob(job) - - //BenchmarkSwitcher.FromAssembly(typeof.Assembly).Run(argv, cfg) |> ignore; - - //exit 0 - //``Texture Tests``.runAllTests() - //testCompile() - - //RadixSortTest.run() - - //testDownloadSlice() - //testCopySlice() - - BenchmarkRunner.Run() |> ignore - - - //testTextureCubeArray() - - //RenderingTests.``[GL] concurrent group change``() - //RenderingTests.``[GL] memory leak test``() - //MultipleStageAgMemoryLeakTest.run() |> ignore + let runManuallyInMain = true - //PerformanceTests.PerformanceTest.runConsole() - //Examples.PerformanceTest.run() - //PerformanceTests.StartupPerformance.runConsole args - //PerformanceTests.IsActiveFlagPerformance.run args - //UseTest.bla() - 0 + if runManuallyInMain then + runTestsSynchronously false allTests + else + runTestsWithCLIArgs [ CLIArguments.No_Spinner ] argv allTests diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Application.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Application.fs index c36ccddb..445f5f90 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Application.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Application.fs @@ -30,7 +30,7 @@ module TestApplication = RuntimeConfig.UseNewRenderTask <- true RuntimeConfig.PreferHostSideTextureCompression <- true - Toolkit.Init(ToolkitOptions(Backend = PlatformBackend.PreferNative)) |> ignore + let toolkit = Toolkit.Init(ToolkitOptions(Backend = PlatformBackend.PreferNative)) let runtime = new Runtime(debug) let ctx = new Context(runtime, fun () -> ContextHandleOpenTK.create runtime.DebugConfig) @@ -59,10 +59,10 @@ module TestApplication = runtime, { new IDisposable with member x.Dispose() = + checkForErrors() runtime.Dispose() checkForDebugErrors() - checkForErrors() - ctx.Dispose() + toolkit.Dispose() } ) @@ -70,6 +70,7 @@ module TestApplication = open Aardvark.Rendering.Vulkan let create (debug : IDebugConfig) = + CustomDeviceChooser.Register Seq.head let app = new HeadlessVulkanApplication(debug) let onExit = { new IDisposable with @@ -91,9 +92,13 @@ module TestApplication = app.Runtime, onExit ) + let mutable private aardvarkInitialized = false + let create' (debug : IDebugConfig) (backend : Backend) = - IntrospectionProperties.CustomEntryAssembly <- Assembly.GetAssembly(typeof) - Aardvark.Init() + if not aardvarkInitialized then + IntrospectionProperties.CustomEntryAssembly <- Assembly.GetAssembly(typeof) + Aardvark.Init() + aardvarkInitialized <- true match backend with | Backend.GL -> GL.create debug diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Common.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Common.fs index 6b9ab79c..9aae94c1 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Common.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Common.fs @@ -22,6 +22,7 @@ module ``Unit Test Utilities`` = let prepareCases (backend : Backend) (name : string) (cases : List unit)>) = cases |> List.map (fun (name, test) -> testCase name (fun () -> TestApplication.createUse test backend) + |> testSequenced ) |> testList name @@ -32,5 +33,6 @@ module ``Unit Test Utilities`` = Aardvark.Init() test() ) + |> testSequenced ) |> testList name \ No newline at end of file diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Buffers.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Buffers.fs index 7b4ea82d..00c151d1 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Buffers.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Buffers.fs @@ -1,4 +1,4 @@ -namespace Aardvark.Rendering.Tests.Rendering +namespace Aardvark.Rendering.Tests.Compute open System open Aardvark.Base diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/ComputeTests.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/ComputeTests.fs index 434d9359..b5726b04 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/ComputeTests.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/ComputeTests.fs @@ -1,7 +1,7 @@ namespace Aardvark.Rendering.Tests open Aardvark.Application -open Aardvark.Rendering.Tests.Rendering +open Aardvark.Rendering.Tests.Compute open Expecto module ``Compute Tests`` = diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Images.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Images.fs index 6ce0e97c..d6110eac 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Images.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Images.fs @@ -1,4 +1,4 @@ -namespace Aardvark.Rendering.Tests.Rendering +namespace Aardvark.Rendering.Tests.Compute open System open Aardvark.Base diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Jpeg.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Jpeg.fs index 552f3e44..269a812b 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Jpeg.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Jpeg.fs @@ -1,4 +1,4 @@ -namespace Aardvark.Rendering.Tests.Rendering +namespace Aardvark.Rendering.Tests.Compute open Aardvark.Base open Aardvark.GPGPU diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/MutableInputBinding.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/MutableInputBinding.fs index 1fb02b8d..2e530cdc 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/MutableInputBinding.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/MutableInputBinding.fs @@ -1,4 +1,4 @@ -namespace Aardvark.Rendering.Tests.Rendering +namespace Aardvark.Rendering.Tests.Compute open Aardvark.Base open Aardvark.Rendering diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Primitives.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Primitives.fs index f3dc5420..df8471e1 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Primitives.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Primitives.fs @@ -1,4 +1,4 @@ -namespace Aardvark.Rendering.Tests.Rendering +namespace Aardvark.Rendering.Tests.Compute open Aardvark.Base open Aardvark.GPGPU diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Sorting.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Sorting.fs index 1a81b20d..c1490a90 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Sorting.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Compute/Sorting.fs @@ -1,4 +1,4 @@ -namespace Aardvark.Rendering.Tests.Rendering +namespace Aardvark.Rendering.Tests.Compute open Aardvark.Base open Aardvark.GPGPU diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Other/Camera.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Other/Camera.fs new file mode 100644 index 00000000..a0e3660d --- /dev/null +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Other/Camera.fs @@ -0,0 +1,68 @@ +namespace Aardvark.Rendering.Tests + +open Aardvark.Base +open Aardvark.Rendering +open Expecto + +module ``Camera Tests`` = + + module private Expect = + + let frustumClose (accuracy : Accuracy) (a : Frustum) (b : Frustum) (message : string) = + Expect.floatClose accuracy a.near b.near (message + " (near)") + Expect.floatClose accuracy a.far b.far (message + " (far)") + Expect.floatClose accuracy a.left b.left (message + " (left)") + Expect.floatClose accuracy a.right b.right (message + " (right)") + Expect.floatClose accuracy a.top b.top (message + " (top)") + Expect.floatClose accuracy a.bottom b.bottom (message + " (bottom)") + Expect.equal a.isOrtho b.isOrtho (message + " (isOrtho)") + + let aspect = + test "aspect" { + let f = Frustum.perspective 75.0 0.1 100.0 1.77 + let a = Frustum.aspect f + Expect.floatClose Accuracy.high a 1.77 "Aspect is wrong" + } + + let fieldOfView = + test "fieldOfView" { + let f = Frustum.perspective 75.0 0.1 100.0 1.77 + let fov = Frustum.horizontalFieldOfViewInDegrees f + Expect.floatClose Accuracy.high fov 75.0 "Field of view is wrong" + } + + let withAspect = + test "withAspect" { + let a = Frustum.perspective 75.0 0.1 100.0 1.77 + let b = Frustum.perspective 75.0 0.1 100.0 1.5 + let c = a |> Frustum.withAspect 1.5 + Expect.frustumClose Accuracy.high b c "Frustums do not match" + } + + let withNear = + test "withNear" { + let a = Frustum.perspective 75.0 0.1 100.0 1.77 + let b = Frustum.perspective 75.0 0.01 100.0 1.77 + let c = a |> Frustum.withNear 0.01 + Expect.frustumClose Accuracy.high b c "Frustums do not match" + } + + let withFieldOfView = + test "withFieldOfView" { + let a = Frustum.perspective 75.0 0.1 100.0 1.77 + let b = Frustum.perspective 90.0 0.1 100.0 1.77 + let c = a |> Frustum.withHorizontalFieldOfViewInDegrees 90.0 + Expect.frustumClose Accuracy.high b c "Frustums do not match" + } + + [] + let tests = + testList "Camera" [ + testList "Frustum" [ + aspect + fieldOfView + withAspect + withNear + withFieldOfView + ] + ] \ No newline at end of file diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Blending.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Blending.fs new file mode 100644 index 00000000..ecbe6404 --- /dev/null +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Blending.fs @@ -0,0 +1,125 @@ +namespace Aardvark.Rendering.Tests.Rendering + +open Aardvark.Base +open Aardvark.Rendering +open Aardvark.Rendering.Tests +open Aardvark.SceneGraph +open Aardvark.Application +open FSharp.Data.Adaptive +open FSharp.Data.Adaptive.Operators +open Expecto + +module Blending = + + module private Semantic = + let Output0 = Sym.ofString "Output0" + let Output1 = Sym.ofString "Output1" + + module private BlendMode = + let ColorMulAlphaAdd = + { BlendMode.Blend with + SourceColorFactor = BlendFactor.Zero + SourceAlphaFactor = BlendFactor.One + DestinationColorFactor = BlendFactor.SourceColor + DestinationAlphaFactor = BlendFactor.One } + + module private Shader = + open FShade + + let inline output01 (c1 : ^Color) (c2 : ^Color) (v : Effects.Vertex) = + let c1 = v4d c1 + let c2 = v4d c2 + fragment { + return {| Output0 = c1; Output1 = c2 |} + } + + module Cases = + + let globalBlend (mode : BlendMode) (colorOp : float32 -> float32 -> float32) (alphaOp : float32 -> float32 -> float32) (runtime : IRuntime) = + let clearColor = C4f(0.1, 0.2, 0.3, 0.4) + let blendedColor = C4f(0.1, 0.1, 0.1, 0.1) + let expectedColor = + C4f( + colorOp clearColor.R blendedColor.R, + colorOp clearColor.G blendedColor.G, + colorOp clearColor.B blendedColor.B, + alphaOp clearColor.A blendedColor.A + ) + + use signature = + runtime.CreateFramebufferSignature([ + DefaultSemantic.Colors, TextureFormat.Rgba32f + ]) + + use task = + Sg.fullScreenQuad + |> Sg.shader { + do! DefaultSurfaces.constantColor blendedColor + } + |> Sg.blendMode' mode + |> Sg.compile runtime signature + + let output = + let clear = clear { color clearColor } + task |> RenderTask.renderToColorWithClear (~~V2i(256)) clear + + output.Acquire() + + try + let result = output.GetValue().Download().AsPixImage() + result |> PixImage.isColor32f Accuracy.medium (expectedColor.ToArray()) + finally + output.Release() + + let perAttachmentBlend (runtime : IRuntime) = + let clearColor = C4f(0.1, 0.2, 0.3, 0.4) + let blendedColor1 = C4f(0.1, 0.1, 0.1, 0.1) + let blendedColor2 = C4f(0.5, 0.5, 0.5, 0.5) + let expectedColor1 = clearColor + blendedColor1 + let expectedColor2 = clearColor * blendedColor2 + + use signature = + runtime.CreateFramebufferSignature([ + Semantic.Output0, TextureFormat.Rgba32f + Semantic.Output1, TextureFormat.Rgba32f + ]) + + let modes = + Map.ofList [ + Semantic.Output0, BlendMode.Add + Semantic.Output1, BlendMode.Multiply + ] + + use task = + Sg.fullScreenQuad + |> Sg.shader { + do! Shader.output01 blendedColor1 blendedColor2 + } + |> Sg.blendModes' modes + |> Sg.compile runtime signature + + let r1, r2 = + let fbo = runtime.CreateFramebuffer(signature, ~~V2i(256)) + let clear = clear { color clearColor } + let output = task |> RenderTask.renderToWithClear fbo clear + output.GetOutputTexture(Semantic.Output0), output.GetOutputTexture(Semantic.Output1) + + r1.Acquire(); r2.Acquire() + + try + let p1 = r1.GetValue().Download().AsPixImage() + p1 |> PixImage.isColor (expectedColor1.ToArray()) + + let p2 = r2.GetValue().Download().AsPixImage() + p2 |> PixImage.isColor (expectedColor2.ToArray()) + + finally + r1.Release(); r2.Release() + + let tests (backend : Backend) = + [ + "Global (add)", Cases.globalBlend BlendMode.Add (+) (+) + "Global (color multiply, alpha add)", Cases.globalBlend BlendMode.ColorMulAlphaAdd (*) (+) + "Per attachment", Cases.perAttachmentBlend + ] + |> prepareCases backend "Blending" \ No newline at end of file diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/ColorMasks.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/ColorMasks.fs new file mode 100644 index 00000000..1643d479 --- /dev/null +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/ColorMasks.fs @@ -0,0 +1,95 @@ +namespace Aardvark.Rendering.Tests.Rendering + +open Aardvark.Base +open Aardvark.Rendering +open Aardvark.Rendering.Tests +open Aardvark.SceneGraph +open Aardvark.Application +open FSharp.Data.Adaptive +open FSharp.Data.Adaptive.Operators + +module ColorMasks = + + module private Semantic = + let Output0 = Sym.ofString "Output0" + let Output1 = Sym.ofString "Output1" + + module private Shader = + open FShade + + let output01White (v : Effects.Vertex) = + fragment { + return {| Output0 = V4d.One; Output1 = V4d.One |} + } + + module Cases = + + let globalMask (runtime : IRuntime) = + use signature = + runtime.CreateFramebufferSignature([ + DefaultSemantic.Colors, TextureFormat.Rgba8 + ]) + + use task = + Sg.fullScreenQuad + |> Sg.shader { + do! DefaultSurfaces.constantColor C4f.White + } + |> Sg.colorMask' (ColorMask.Green ||| ColorMask.Alpha) + |> Sg.compile runtime signature + + let size = ~~V2i(256) + let output = task |> RenderTask.renderToColor size + output.Acquire() + + try + let result = output.GetValue().Download().AsPixImage() + result |> PixImage.isColor [| 0uy; 255uy; 0uy; 255uy |] + finally + output.Release() + + let perAttachmentMask (runtime : IRuntime) = + use signature = + runtime.CreateFramebufferSignature([ + Semantic.Output0, TextureFormat.Rgba8 + Semantic.Output1, TextureFormat.Rgba8 + ]) + + let masks = + Map.ofList [ + Semantic.Output0, ColorMask.Green ||| ColorMask.Alpha + Semantic.Output1, ColorMask.Red ||| ColorMask.Blue ||| ColorMask.Alpha + ] + + use task = + Sg.fullScreenQuad + |> Sg.shader { + do! Shader.output01White + } + |> Sg.colorMasks' masks + |> Sg.compile runtime signature + + let r1, r2 = + let fbo = runtime.CreateFramebuffer(signature, ~~V2i(256)) + let clear = ClearValues.ofColor V4f.Zero + let output = task |> RenderTask.renderToWithClear fbo clear + output.GetOutputTexture(Semantic.Output0), output.GetOutputTexture(Semantic.Output1) + + r1.Acquire(); r2.Acquire() + + try + let p1 = r1.GetValue().Download().AsPixImage() + p1 |> PixImage.isColor [| 0uy; 255uy; 0uy; 255uy |] + + let p2 = r2.GetValue().Download().AsPixImage() + p2 |> PixImage.isColor [| 255uy; 0uy; 255uy; 255uy |] + + finally + r1.Release(); r2.Release() + + let tests (backend : Backend) = + [ + "Global", Cases.globalMask + "Per attachment", Cases.perAttachmentMask + ] + |> prepareCases backend "Color masks" \ No newline at end of file diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Samplers.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Samplers.fs index 891149eb..b68400c3 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Samplers.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Samplers.fs @@ -20,11 +20,13 @@ module Samplers = let private diffuseIntSampler = intSampler2d { texture uniform?DiffuseColorTexture + filter Filter.MinMagPoint } let private diffuseUIntSampler = uintSampler2d { texture uniform?DiffuseColorTexture + filter Filter.MinMagPoint } let diffuseIntTexture (v : Vertex) = diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Signatures.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Signatures.fs index 7653e3c2..64b17fbb 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Signatures.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Rendering/Signatures.fs @@ -101,7 +101,8 @@ module FramebufferSignature = let r1, r3 = let fbo = runtime.CreateFramebuffer(signature, ~~V2i(256)) - let output = task |> RenderTask.renderTo fbo + let clear = ClearValues.ofColor V4f.Zero + let output = task |> RenderTask.renderToWithClear fbo clear output.GetOutputTexture(Semantic.Output1), output.GetOutputTexture(Semantic.Output3) r1.Acquire(); r3.Acquire() @@ -141,7 +142,8 @@ module FramebufferSignature = let result = let fbo = runtime.CreateFramebuffer(signature, ~~V2i(256)) - let output = task |> RenderTask.renderTo fbo + let clear = ClearValues.ofColor V4f.Zero + let output = task |> RenderTask.renderToWithClear fbo clear output.GetOutputTexture(Semantic.Output1) result.Acquire() @@ -164,6 +166,9 @@ module FramebufferSignature = 4, { Name = Semantic.Output4; Format = TextureFormat.Rgba8 } ], Some TextureFormat.Depth24Stencil8) + use tclear = + runtime.CompileClear(signature, ClearValues.ofColor V4f.Zero) + use t0sig = runtime.CreateFramebufferSignature(Map.ofList [ 1, { Name = Semantic.Output1; Format = TextureFormat.Rgba8 } @@ -221,7 +226,7 @@ module FramebufferSignature = |> Sg.compile runtime t2sig use task = - RenderTask.ofList [t0; t1; t2] + RenderTask.ofList [tclear; t0; t1; t2] // Combining tasks with varying signatures leads to task with no signature Expect.equal task.FramebufferSignature None "Unexpected render task signature" @@ -279,7 +284,8 @@ module FramebufferSignature = let result = let fbo = runtime.CreateFramebuffer(signature, ~~V2i(256)) - let output = task |> RenderTask.renderTo fbo + let clear = ClearValues.ofColor V4f.Zero + let output = task |> RenderTask.renderToWithClear fbo clear output.GetOutputTexture(Semantic.Output1) result.Acquire() diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Clear.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Clear.fs index 1b4a3f92..714bfb9f 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Clear.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Clear.fs @@ -23,7 +23,7 @@ module TextureClear = let rgba8 (runtime : IRuntime) = let data = createAndClearColor runtime TextureFormat.Rgba8 <| C3f(0.5) - data.AsPixImage() |> PixImage.isColor (C4b(127uy).ToArray()) + data.AsPixImage() |> PixImage.isColorWithDist 1L (C4b(127uy).ToArray()) let rgba32i (runtime : IRuntime) = let data = createAndClearColor runtime TextureFormat.Rgba32i <| V4i(-1) @@ -135,7 +135,7 @@ module TextureClear = do let c = C4b(127uy) let pi = textures.[c3].Download().AsPixImage() - pi |> PixImage.isColor (c.ToArray()) + pi |> PixImage.isColorWithDist 1L (c.ToArray()) do let c = C3b.AliceBlue |> V4i diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Create.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Create.fs index ccac0fad..691a2d04 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Create.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Create.fs @@ -115,11 +115,99 @@ module TextureCreate = finally runtime.DeleteTexture(t) + let memoryUsage (runtime : IRuntime) = + let runtime = unbox runtime + let context = runtime.Context + + let mutable count = 0 + let mutable memory = 0L + + let check() = + Expect.equal context.MemoryUsage.TextureCount count "unexpected texture count" + Expect.equal context.MemoryUsage.TextureMemory memory "unexpected memory usage" + + // Simple 2D + let t1 = runtime.CreateTexture2D(V2i(512), TextureFormat.Rgba8, levels = 1) + count <- count + 1 + memory <- memory + (512L * 512L * 4L) + check() + + // Multisampled 2D + let t2 = runtime.CreateTexture2D(V2i(512), TextureFormat.R8, samples = 2) + count <- count + 1 + memory <- memory + (512L * 512L * 2L) + check() + + // Mipmapped 2D + let t3 = runtime.CreateTexture2D(V2i(512), TextureFormat.Rgba32f, levels = Fun.MipmapLevels 512) + count <- count + 1 + for i = 1 to t3.MipMapLevels do + let size = v3l <| t3.GetSize(i - 1) + memory <- memory + (size.X * size.Y * 16L) + check() + + // Simple 1D + let t4 = runtime.CreateTexture1D(123, TextureFormat.R32f) + count <- count + 1 + memory <- memory + (123L * 4L) + check() + + // Simple 3D + let t5 = runtime.CreateTexture3D(V3i(3, 12, 64), TextureFormat.Rgba8) + count <- count + 1 + memory <- memory + (3L * 12L * 64L * 4L) + check() + + // Cube + let t6 = runtime.CreateTextureCube(64, TextureFormat.Rgba8) + count <- count + 1 + memory <- memory + (64L * 64L * 4L * 6L) + check() + + // Cube array + let t7 = runtime.CreateTextureCubeArray(123, TextureFormat.Rgba8, levels = Fun.MipmapLevels 123, count = 7) + count <- count + 1 + for _ = 1 to t7.Count * 6 do + for i = 1 to t7.MipMapLevels do + let size = v3l <| t7.GetSize(i - 1) + memory <- memory + (size.X * size.X * 4L) + check() + + // Compressed + let t8 = runtime.PrepareTexture <| EmbeddedResource.getTexture TextureParams.mipmappedCompressed "data/bc1.dds" + + let sizeInBytes = + let mode = t8.Format.CompressionMode + + (0L, [0 .. t8.MipMapLevels - 1]) ||> List.fold (fun sizeInBytes level -> + let size = Fun.MipmapLevelSize(t8.Size, level) + sizeInBytes + (int64 <| CompressionMode.sizeInBytes size mode) + ) + + count <- count + 1 + memory <- memory + sizeInBytes + check() + + t1.Dispose() + t2.Dispose() + t3.Dispose() + t4.Dispose() + t5.Dispose() + t6.Dispose() + t7.Dispose() + t8.Dispose() + count <- 0 + memory <- 0L + check() + let tests (backend : Backend) = [ "Non-positive arguments", Cases.nonPositiveArguments "Invalid usage", Cases.invalidUsage "Valid usage", Cases.validUsage "Unsupported multisample count", Cases.unsupportedMultisamples + + if backend = Backend.GL then + "Memory usage", Cases.memoryUsage ] |> prepareCases backend "Create" \ No newline at end of file diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Download.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Download.fs index 524aa2d3..3524a045 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Download.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Download.fs @@ -32,6 +32,10 @@ module TextureDownload = let private texture2DWithFormat<'T when 'T : equality> (runtime : IRuntime) (format : TextureFormat) (data : PixImage<'T>) = texture2DWithFormatWithComparer runtime Expect.equal format data + let inline private texture2DWithFormatNorm (runtime : IRuntime) (format : TextureFormat) (data : PixImage<'T>) = + let comp a b = Expect.isLessThanOrEqual (abs (int64 a - int64 b)) 1L + texture2DWithFormatWithComparer runtime comp format data + let private texture2DWithFormat32f (runtime : IRuntime) (accuracy : Accuracy) (format : TextureFormat) (data : PixImage) = let comp a b = Expect.floatClose accuracy (float a) (float b) texture2DWithFormatWithComparer runtime comp format data @@ -54,7 +58,7 @@ module TextureDownload = let texture2Drgba16snorm (runtime : IRuntime) = let data = PixImage.random16i <| V2i(256) - data |> texture2DWithFormat runtime TextureFormat.Rgba16Snorm + data |> texture2DWithFormatNorm runtime TextureFormat.Rgba16Snorm let texture2Drgba32ui (runtime : IRuntime) = let data = PixImage.random32ui <| V2i(256) diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Upload.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Upload.fs index fc85eb7e..10861e09 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Upload.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Texture/Upload.fs @@ -153,14 +153,12 @@ module TextureUpload = |> shader |> Sg.compile runtime signature + // Render sampling the null texture. + // We can't check the result since null textures are uninitialized, just make sure there are no errors. let buffer = task |> RenderTask.renderToColor (AVal.init <| V2i(256)) buffer.Acquire() - - try - let result = buffer.GetValue().Download().AsPixImage().ToFormat(Col.Format.RGB) - PixImage.isColor [| 0uy; 0uy; 0uy |] result - finally - buffer.Release() + try buffer.GetValue() |> ignore + finally buffer.Release() let texture1DNull (runtime : IRuntime) = let diffuseSampler = diff --git a/src/Tests/Aardvark.Rendering.Tests/Tests/Utilities.fs b/src/Tests/Aardvark.Rendering.Tests/Tests/Utilities.fs index b0826532..7e947408 100644 --- a/src/Tests/Aardvark.Rendering.Tests/Tests/Utilities.fs +++ b/src/Tests/Aardvark.Rendering.Tests/Tests/Utilities.fs @@ -230,6 +230,9 @@ module PixData = let isColor (color : 'T[]) (pi : PixImage<'T>) = isColorWithComparer Expect.equal color pi + let inline isColorWithDist (maxDist : int64) (color : 'T[]) (pi : PixImage<'T>) = + isColorWithComparer (fun x y -> Expect.isLessThanOrEqual (abs (int64 x - int64 y)) maxDist) color pi + let isColor32f (accuracy : Accuracy) (color : float32[]) (pi : PixImage) = let comp a b = Expect.floatClose accuracy (float a) (float b) isColorWithComparer comp color pi @@ -290,6 +293,71 @@ module ``Expecto Extensions`` = Expect.floatClose accuracy actual.Z expected.Z message Expect.floatClose accuracy actual.W expected.W message + // Utility to run Expecto tests synchronously in the current thread. + // Prints a summary at the end. + let runTestsSynchronously (stopOnFail : bool) (tests : Test) = + let failed = ResizeArray() + let skipped = ResizeArray() + let mutable passed = 0 + + let tests = Test.toTestCodeList tests + let count = tests.Length + + let printSummary() = + let print fmt = + Printf.kprintf (fun str -> + if failed.Count = 0 then Log.line "%s" str + else Log.error "%s" str + ) fmt + + Report.Line() + print $"Total: {count}, Passed: {passed}, Failed: {failed.Count}, Skipped: {skipped.Count}" + + if failed.Count > 0 then + Report.Line() + print "Failed tests:" + for t in failed do + print " %s" t + + if skipped.Count > 0 then + Report.Line() + print "Skipped tests:" + for t in skipped do + print " %s" t + + let rec run (index : int) (tests : FlatTest list) = + match tests with + | t :: rem -> + let name = t.fullName "." + let mutable success = false + + Report.Begin $"({index + 1} / {count}) {name}" + + match t.test with + | Sync f -> + try + f() + success <- true + inc &passed + with exn -> + Log.error "%A" exn + failed.Add name + | _ -> + Log.error "Test not supported" + skipped.Add name + + Report.End(if success then " - passed" else " - failed") |> ignore + + if success || not stopOnFail then + run (index + 1) rem + + | _ -> + () + + run 0 tests + printSummary() + if failed.Count = 0 then 0 else 1 + [] module ``RenderTo Utilities`` = open Aardvark.SceneGraph