Compare commits
	
		
			41 Commits 
		
	
	
		
			main
			...
			what_if_no
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 5a97bd3f33 | |
|  | 45b085a236 | |
|  | 679ad2463c | |
|  | 9e4e44bb52 | |
|  | 8813a0139d | |
|  | 217ae96888 | |
|  | 23b6499479 | |
|  | 929d8f5cc9 | |
|  | 69d2c9cc31 | |
|  | 9195e445b2 | |
|  | f30d8f0197 | |
|  | bde31fbe07 | |
|  | a762a80c4f | |
|  | 099c07aa39 | |
|  | cba6ca59d3 | |
|  | a004488f81 | |
|  | 33ed8b2364 | |
|  | 3c832550d0 | |
|  | c84752f38c | |
|  | 019afa91f5 | |
|  | 00adec189c | |
|  | 0e723514df | |
|  | e0f4c19dc2 | |
|  | 178a5ea3cf | |
|  | 50b8cb11c9 | |
|  | d83501437d | |
|  | fe520dc9cc | |
|  | b29341eca3 | |
|  | 22bcd2e471 | |
|  | fe31e23ccc | |
|  | 848b1c706c | |
|  | a207f404b9 | |
|  | 31c79d3179 | |
|  | 8229e5dd33 | |
|  | 1eae01c95c | |
|  | b80527d793 | |
|  | ecfcb666a8 | |
|  | ad97aed60f | |
|  | 0a5ec9e82d | |
|  | 8648eef5d1 | |
|  | 39496c37ea | 
|  | @ -3,7 +3,6 @@ | |||
| 	<PropertyGroup> | ||||
| 		<TargetFramework>net8.0</TargetFramework> | ||||
| 		<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
| 		<LangVersion>11</LangVersion> | ||||
| 	</PropertyGroup> | ||||
| 
 | ||||
| 	<PropertyGroup> | ||||
|  | @ -26,8 +25,8 @@ | |||
| 	</ItemGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_fullscreen.vert.refresh"> | ||||
| 			<LogicalName>MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh</LogicalName> | ||||
| 		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\fullscreen.vert.refresh"> | ||||
| 			<LogicalName>MoonWorks.Graphics.StockShaders.Fullscreen.vert.refresh</LogicalName> | ||||
| 		</EmbeddedResource> | ||||
| 		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.refresh"> | ||||
| 			<LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh</LogicalName> | ||||
|  | @ -38,5 +37,8 @@ | |||
| 		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_msdf.frag.refresh"> | ||||
| 			<LogicalName>MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh</LogicalName> | ||||
| 		</EmbeddedResource> | ||||
| 		<EmbeddedResource Include="src\Graphics\StockShaders\Binary\blit.frag.refresh"> | ||||
| 			<LogicalName>MoonWorks.Graphics.StockShaders.Blit.frag.refresh</LogicalName> | ||||
| 		</EmbeddedResource> | ||||
| 	</ItemGroup> | ||||
| </Project> | ||||
|  |  | |||
|  | @ -5,8 +5,8 @@ | |||
| 	<dllmap dll="SDL2" os="linux,freebsd,netbsd" target="libSDL2-2.0.so.0"/> | ||||
| 
 | ||||
| 	<dllmap dll="Refresh" os="windows" target="Refresh.dll"/> | ||||
| 	<dllmap dll="Refresh" os="osx" target="libRefresh.1.dylib"/> | ||||
| 	<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.1"/> | ||||
| 	<dllmap dll="Refresh" os="osx" target="libRefresh.2.dylib"/> | ||||
| 	<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.2"/> | ||||
| 
 | ||||
| 	<dllmap dll="FAudio" os="windows" target="FAudio.dll"/> | ||||
| 	<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/> | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28 | ||||
| Subproject commit 995a54fa2df82946441c9ec6446d7cd12236f8f7 | ||||
|  | @ -53,10 +53,10 @@ namespace MoonWorks | |||
| 		public Game( | ||||
| 			WindowCreateInfo windowCreateInfo, | ||||
| 			FrameLimiterSettings frameLimiterSettings, | ||||
| 			Span<Backend> preferredBackends, | ||||
| 			int targetTimestep = 60, | ||||
| 			bool debugMode = false | ||||
| 		) | ||||
| 		{ | ||||
| 		) { | ||||
| 			Logger.LogInfo("Initializing frame limiter..."); | ||||
| 			Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep); | ||||
| 			gameTimer = Stopwatch.StartNew(); | ||||
|  | @ -82,7 +82,7 @@ namespace MoonWorks | |||
| 
 | ||||
| 			Logger.LogInfo("Initializing graphics device..."); | ||||
| 			GraphicsDevice = new GraphicsDevice( | ||||
| 				Backend.Vulkan, | ||||
| 				preferredBackends, | ||||
| 				debugMode | ||||
| 			); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,17 +1,21 @@ | |||
| namespace MoonWorks.Graphics | ||||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// A buffer-offset pair to be used when binding vertex buffers. | ||||
| 	/// A buffer-offset pair to be used when binding vertex or index buffers. | ||||
| 	/// </summary> | ||||
| 	public struct BufferBinding | ||||
| 	public readonly record struct BufferBinding( | ||||
| 		GpuBuffer Buffer, | ||||
| 		uint Offset | ||||
| 	) { | ||||
| 		public Refresh.BufferBinding ToRefresh() | ||||
| 		{ | ||||
| 		public Buffer Buffer; | ||||
| 		public ulong Offset; | ||||
| 
 | ||||
| 		public BufferBinding(Buffer buffer, ulong offset) | ||||
| 			return new Refresh.BufferBinding | ||||
| 			{ | ||||
| 			Buffer = buffer; | ||||
| 			Offset = offset; | ||||
| 				gpuBuffer = Buffer.Handle, | ||||
| 				offset = Offset | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,35 @@ | |||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// Binding specification to be used when binding buffers for compute shaders. | ||||
| 	/// </summary> | ||||
| 	/// <param name="GpuBuffer">The GpuBuffer to bind.</param> | ||||
| 	/// <param name="WriteOption"> | ||||
| 	///   Specifies data dependency behavior when this buffer is written to in the shader. <br/> | ||||
| 	/// | ||||
| 	///   Cycle: | ||||
| 	///     If this buffer has been used in commands that have not finished, | ||||
| 	///     the implementation may choose to prevent a dependency on those commands | ||||
| 	///     at the cost of increased memory usage. | ||||
| 	///     You may NOT assume that any of the previous data is retained. | ||||
| 	///     This may prevent stalls when frequently updating a resource.  <br /> | ||||
| 	/// | ||||
| 	///   SafeOverwrite: | ||||
| 	///     Overwrites the data safely using a GPU memory barrier. | ||||
| 	/// </param> | ||||
| 	public readonly record struct ComputeBufferBinding( | ||||
| 		GpuBuffer GpuBuffer, | ||||
| 		WriteOptions WriteOption | ||||
| 	) { | ||||
| 		public Refresh.ComputeBufferBinding ToRefresh() | ||||
| 		{ | ||||
| 			return new Refresh.ComputeBufferBinding | ||||
| 			{ | ||||
| 				gpuBuffer = GpuBuffer.Handle, | ||||
| 				writeOption = (Refresh.WriteOptions) WriteOption | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,35 @@ | |||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// Binding specification used for binding texture slices for compute shaders. | ||||
| 	/// </summary> | ||||
| 	/// <param name="TextureSlice">The TextureSlice to bind.</param> | ||||
| 	/// <param name="WriteOption"> | ||||
| 	///   Specifies data dependency behavior when this texture is written to in the shader. <br/> | ||||
| 	/// | ||||
| 	///   Cycle: | ||||
| 	///     If this buffer has been used in commands that have not finished, | ||||
| 	///     the implementation may choose to prevent a dependency on those commands | ||||
| 	///     at the cost of increased memory usage. | ||||
| 	///     You may NOT assume that any of the previous data is retained. | ||||
| 	///     This may prevent stalls when frequently updating a resource. <br /> | ||||
| 	/// | ||||
| 	///   SafeOverwrite: | ||||
| 	///     Overwrites the data safely using a GPU memory barrier. | ||||
| 	/// </param> | ||||
| 	public readonly record struct ComputeTextureBinding( | ||||
| 		TextureSlice TextureSlice, | ||||
| 		WriteOptions WriteOption | ||||
| 	) { | ||||
| 		public Refresh.ComputeTextureBinding ToRefresh() | ||||
| 		{ | ||||
| 			return new Refresh.ComputeTextureBinding | ||||
| 			{ | ||||
| 				textureSlice = TextureSlice.ToRefreshTextureSlice(), | ||||
| 				writeOption = (Refresh.WriteOptions) WriteOption | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,17 +1,21 @@ | |||
| namespace MoonWorks.Graphics | ||||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// A texture-sampler pair to be used when binding samplers. | ||||
| 	/// </summary> | ||||
| 	public struct TextureSamplerBinding | ||||
| 	public readonly record struct TextureSamplerBinding( | ||||
| 		Texture Texture, | ||||
| 		Sampler Sampler | ||||
| 	) { | ||||
| 		public Refresh.TextureSamplerBinding ToRefresh() | ||||
| 		{ | ||||
| 		public Texture Texture; | ||||
| 		public Sampler Sampler; | ||||
| 
 | ||||
| 		public TextureSamplerBinding(Texture texture, Sampler sampler) | ||||
| 			return new Refresh.TextureSamplerBinding | ||||
| 			{ | ||||
| 			Texture = texture; | ||||
| 			Sampler = sampler; | ||||
| 				texture = Texture.Handle, | ||||
| 				sampler = Sampler.Handle | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -47,7 +47,13 @@ namespace MoonWorks.Graphics.Font | |||
| 				out float distanceRange | ||||
| 			); | ||||
| 
 | ||||
| 			var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png")); | ||||
| 			var imagePath = Path.ChangeExtension(fontPath, ".png"); | ||||
| 			ImageUtils.ImageInfoFromFile(imagePath, out var width, out var height, out var sizeInBytes); | ||||
| 
 | ||||
| 			var uploader = new ResourceUploader(graphicsDevice); | ||||
| 			var texture = uploader.CreateTexture2DFromCompressed(imagePath); | ||||
| 			uploader.Upload(); | ||||
| 			uploader.Dispose(); | ||||
| 
 | ||||
| 			NativeMemory.Free(fontFileByteBuffer); | ||||
| 			NativeMemory.Free(atlasFileByteBuffer); | ||||
|  |  | |||
|  | @ -13,10 +13,12 @@ namespace MoonWorks.Graphics.Font | |||
| 		private GraphicsDevice GraphicsDevice { get; } | ||||
| 		public IntPtr Handle { get; } | ||||
| 
 | ||||
| 		public Buffer VertexBuffer { get; protected set; } = null; | ||||
| 		public Buffer IndexBuffer { get; protected set; } = null; | ||||
| 		public GpuBuffer VertexBuffer { get; protected set; } = null; | ||||
| 		public GpuBuffer IndexBuffer { get; protected set; } = null; | ||||
| 		public uint PrimitiveCount { get; protected set; } | ||||
| 
 | ||||
| 		private TransferBuffer TransferBuffer; | ||||
| 
 | ||||
| 		public Font CurrentFont { get; private set; } | ||||
| 
 | ||||
| 		private byte* StringBytes; | ||||
|  | @ -30,8 +32,10 @@ namespace MoonWorks.Graphics.Font | |||
| 			StringBytesLength = 128; | ||||
| 			StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength); | ||||
| 
 | ||||
| 			VertexBuffer = Buffer.Create<Vertex>(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT); | ||||
| 			IndexBuffer = Buffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT); | ||||
| 			VertexBuffer = GpuBuffer.Create<Vertex>(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT); | ||||
| 			IndexBuffer = GpuBuffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT); | ||||
| 
 | ||||
| 			TransferBuffer = TransferBuffer.Create<byte>(GraphicsDevice, TransferUsage.Buffer, VertexBuffer.Size + IndexBuffer.Size); | ||||
| 		} | ||||
| 
 | ||||
| 		// Call this to initialize or reset the batch. | ||||
|  | @ -93,22 +97,38 @@ namespace MoonWorks.Graphics.Font | |||
| 				out uint indexDataLengthInBytes | ||||
| 			); | ||||
| 
 | ||||
| 			var vertexSpan = new Span<byte>((void*) vertexDataPointer, (int) vertexDataLengthInBytes); | ||||
| 			var indexSpan = new Span<byte>((void*) indexDataPointer, (int) indexDataLengthInBytes); | ||||
| 
 | ||||
| 			var newTransferBufferNeeded = false; | ||||
| 
 | ||||
| 			if (VertexBuffer.Size < vertexDataLengthInBytes) | ||||
| 			{ | ||||
| 				VertexBuffer.Dispose(); | ||||
| 				VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes); | ||||
| 				VertexBuffer = new GpuBuffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes); | ||||
| 				newTransferBufferNeeded = true; | ||||
| 			} | ||||
| 
 | ||||
| 			if (IndexBuffer.Size < indexDataLengthInBytes) | ||||
| 			{ | ||||
| 				IndexBuffer.Dispose(); | ||||
| 				IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes); | ||||
| 				IndexBuffer = new GpuBuffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes); | ||||
| 				newTransferBufferNeeded = true; | ||||
| 			} | ||||
| 
 | ||||
| 			if (newTransferBufferNeeded) | ||||
| 			{ | ||||
| 				TransferBuffer.Dispose(); | ||||
| 				TransferBuffer = new TransferBuffer(GraphicsDevice, TransferUsage.Buffer, VertexBuffer.Size + IndexBuffer.Size); | ||||
| 			} | ||||
| 
 | ||||
| 			if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0) | ||||
| 			{ | ||||
| 				commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes); | ||||
| 				commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes); | ||||
| 				TransferBuffer.SetData(vertexSpan, TransferOptions.Cycle); | ||||
| 				TransferBuffer.SetData(indexSpan, (uint) vertexSpan.Length, TransferOptions.Unsafe); | ||||
| 
 | ||||
| 				commandBuffer.UploadToBuffer(TransferBuffer, VertexBuffer, new BufferCopy(0, 0, (uint) vertexSpan.Length), WriteOptions.Cycle); | ||||
| 				commandBuffer.UploadToBuffer(TransferBuffer, IndexBuffer, new BufferCopy((uint) vertexSpan.Length, 0, (uint) indexSpan.Length), WriteOptions.Cycle); | ||||
| 			} | ||||
| 
 | ||||
| 			PrimitiveCount = vertexCount / 2; | ||||
|  | @ -123,12 +143,12 @@ namespace MoonWorks.Graphics.Font | |||
| 			)); | ||||
| 			commandBuffer.BindVertexBuffers(VertexBuffer); | ||||
| 			commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo); | ||||
| 			commandBuffer.PushVertexShaderUniforms(transformMatrix); | ||||
| 			commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange); | ||||
| 			commandBuffer.DrawIndexedPrimitives( | ||||
| 				0, | ||||
| 				0, | ||||
| 				PrimitiveCount, | ||||
| 				commandBuffer.PushVertexShaderUniforms(transformMatrix), | ||||
| 				commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange) | ||||
| 				PrimitiveCount | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ namespace MoonWorks.Graphics | |||
| 	{ | ||||
| 		public IntPtr Handle { get; } | ||||
| 		public Backend Backend { get; } | ||||
| 		public bool DebugMode { get; } | ||||
| 
 | ||||
| 		private uint windowFlags; | ||||
| 		public SDL2.SDL.SDL_WindowFlags WindowFlags => (SDL2.SDL.SDL_WindowFlags) windowFlags; | ||||
|  | @ -22,6 +23,9 @@ namespace MoonWorks.Graphics | |||
| 		// Built-in video pipeline | ||||
| 		internal GraphicsPipeline VideoPipeline { get; } | ||||
| 
 | ||||
| 		// Built-in blit pipeline | ||||
| 		internal GraphicsPipeline BlitPipeline { get; } | ||||
| 
 | ||||
| 		// Built-in text shader info | ||||
| 		public GraphicsShaderInfo TextVertexShaderInfo { get; } | ||||
| 		public GraphicsShaderInfo TextFragmentShaderInfo { get; } | ||||
|  | @ -37,11 +41,17 @@ namespace MoonWorks.Graphics | |||
| 		private FencePool FencePool; | ||||
| 		private CommandBufferPool CommandBufferPool; | ||||
| 
 | ||||
| 		internal GraphicsDevice( | ||||
| 			Backend preferredBackend, | ||||
| 		internal unsafe GraphicsDevice( | ||||
| 			Span<Backend> preferredBackends, | ||||
| 			bool debugMode | ||||
| 		) { | ||||
| 			Backend = (Backend) Refresh.Refresh_SelectBackend((Refresh.Backend) preferredBackend, out windowFlags); | ||||
| 			var backends = stackalloc Refresh.Backend[preferredBackends.Length]; | ||||
| 			for (var i = 0; i < preferredBackends.Length; i += 1) | ||||
| 			{ | ||||
| 				backends[i] = (Refresh.Backend) preferredBackends[i]; | ||||
| 			} | ||||
| 
 | ||||
| 			Backend = (Backend) Refresh.Refresh_SelectBackend(backends, (uint) preferredBackends.Length, out windowFlags); | ||||
| 
 | ||||
| 			if (Backend == Backend.Invalid) | ||||
| 			{ | ||||
|  | @ -52,37 +62,49 @@ namespace MoonWorks.Graphics | |||
| 				Conversions.BoolToByte(debugMode) | ||||
| 			); | ||||
| 
 | ||||
| 			DebugMode = debugMode; | ||||
| 			// TODO: check for CreateDevice fail | ||||
| 
 | ||||
| 			// Check for replacement stock shaders | ||||
| 			string basePath = System.AppContext.BaseDirectory; | ||||
| 
 | ||||
| 			string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh"); | ||||
| 			string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh"); | ||||
| 			string fullscreenVertPath = Path.Combine(basePath, "fullscreen.vert.refresh"); | ||||
| 
 | ||||
| 			string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh"); | ||||
| 			string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh"); | ||||
| 
 | ||||
| 			ShaderModule videoVertShader; | ||||
| 			ShaderModule videoFragShader; | ||||
| 			string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh"); | ||||
| 			string blitFragPath = Path.Combine(basePath, "blit.frag.refresh"); | ||||
| 
 | ||||
| 			ShaderModule fullscreenVertShader; | ||||
| 
 | ||||
| 			ShaderModule textVertShader; | ||||
| 			ShaderModule textFragShader; | ||||
| 
 | ||||
| 			if (File.Exists(videoVertPath) && File.Exists(videoFragPath)) | ||||
| 			ShaderModule videoFragShader; | ||||
| 			ShaderModule blitFragShader; | ||||
| 
 | ||||
| 			if (File.Exists(fullscreenVertPath)) | ||||
| 			{ | ||||
| 				fullscreenVertShader = new ShaderModule(this, fullscreenVertPath); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// use defaults | ||||
| 				var assembly = typeof(GraphicsDevice).Assembly; | ||||
| 				using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.Fullscreen.vert.refresh"); | ||||
| 				fullscreenVertShader = new ShaderModule(this, vertStream); | ||||
| 			} | ||||
| 
 | ||||
| 			if (File.Exists(videoFragPath)) | ||||
| 			{ | ||||
| 				videoVertShader = new ShaderModule(this, videoVertPath); | ||||
| 				videoFragShader = new ShaderModule(this, videoFragPath); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// use defaults | ||||
| 				var assembly = typeof(GraphicsDevice).Assembly; | ||||
| 
 | ||||
| 				using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh"); | ||||
| 				using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh"); | ||||
| 
 | ||||
| 				videoVertShader = new ShaderModule(this, vertStream); | ||||
| 				videoFragShader = new ShaderModule(this, fragStream); | ||||
| 			} | ||||
| 
 | ||||
|  | @ -103,6 +125,19 @@ namespace MoonWorks.Graphics | |||
| 				textFragShader = new ShaderModule(this, fragStream); | ||||
| 			} | ||||
| 
 | ||||
| 			if (File.Exists(blitFragPath)) | ||||
| 			{ | ||||
| 				blitFragShader = new ShaderModule(this, blitFragPath); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// use defaults | ||||
| 				var assembly = typeof(GraphicsDevice).Assembly; | ||||
| 
 | ||||
| 				using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.Blit.frag.refresh"); | ||||
| 				blitFragShader = new ShaderModule(this, fragStream); | ||||
| 			} | ||||
| 
 | ||||
| 			VideoPipeline = new GraphicsPipeline( | ||||
| 				this, | ||||
| 				new GraphicsPipelineCreateInfo | ||||
|  | @ -115,7 +150,7 @@ namespace MoonWorks.Graphics | |||
| 					), | ||||
| 					DepthStencilState = DepthStencilState.Disable, | ||||
| 					VertexShaderInfo = GraphicsShaderInfo.Create( | ||||
| 						videoVertShader, | ||||
| 						fullscreenVertShader, | ||||
| 						"main", | ||||
| 						0 | ||||
| 					), | ||||
|  | @ -131,6 +166,34 @@ namespace MoonWorks.Graphics | |||
| 				} | ||||
| 			); | ||||
| 
 | ||||
| 			BlitPipeline = new GraphicsPipeline( | ||||
| 				this, | ||||
| 				new GraphicsPipelineCreateInfo | ||||
| 				{ | ||||
| 					AttachmentInfo = new GraphicsPipelineAttachmentInfo( | ||||
| 						new ColorAttachmentDescription( | ||||
| 							TextureFormat.R8G8B8A8, | ||||
| 							ColorAttachmentBlendState.None | ||||
| 						) | ||||
| 					), | ||||
| 					DepthStencilState = DepthStencilState.Disable, | ||||
| 					VertexShaderInfo = GraphicsShaderInfo.Create( | ||||
| 						fullscreenVertShader, | ||||
| 						"main", | ||||
| 						0 | ||||
| 					), | ||||
| 					FragmentShaderInfo = GraphicsShaderInfo.Create( | ||||
| 						blitFragShader, | ||||
| 						"main", | ||||
| 						1 | ||||
| 					), | ||||
| 					VertexInputState = VertexInputState.Empty, | ||||
| 					RasterizerState = RasterizerState.CCW_CullNone, | ||||
| 					PrimitiveType = PrimitiveType.TriangleList, | ||||
| 					MultisampleState = MultisampleState.None | ||||
| 				} | ||||
| 			); | ||||
| 
 | ||||
| 			TextVertexShaderInfo = GraphicsShaderInfo.Create<Math.Float.Matrix4x4>(textVertShader, "main", 0); | ||||
| 			TextFragmentShaderInfo = GraphicsShaderInfo.Create<float>(textFragShader, "main", 1); | ||||
| 			TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>(); | ||||
|  | @ -412,6 +475,118 @@ namespace MoonWorks.Graphics | |||
| 			FencePool.Return(fence); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// ⚠️⚠️⚠️ <br/> | ||||
| 		/// Downloads data from a Texture to a TransferBuffer. | ||||
| 		/// This copy occurs immediately on the CPU timeline.<br/> | ||||
| 		/// | ||||
| 		/// If you modify this texture in a command buffer and then call this function without calling | ||||
| 		/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/> | ||||
| 		/// | ||||
| 		/// This method forces a sync point and is generally a bad thing to do. | ||||
| 		/// Only use it if you have exhausted all other options.<br/> | ||||
| 		/// | ||||
| 		/// Remember: friends don't let friends readback.<br/> | ||||
| 		/// ⚠️⚠️⚠️ | ||||
| 		/// </summary> | ||||
| 		public void DownloadFromTexture( | ||||
| 			in TextureRegion textureRegion, | ||||
| 			TransferBuffer transferBuffer, | ||||
| 			in BufferImageCopy copyParams, | ||||
| 			TransferOptions transferOption | ||||
| 		) { | ||||
| 			Refresh.Refresh_DownloadFromTexture( | ||||
| 				Handle, | ||||
| 				textureRegion.ToRefreshTextureRegion(), | ||||
| 				transferBuffer.Handle, | ||||
| 				copyParams.ToRefresh(), | ||||
| 				(Refresh.TransferOptions) transferOption | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// ⚠️⚠️⚠️ <br/> | ||||
| 		/// Downloads all data from a 2D texture with no mips to a TransferBuffer. | ||||
| 		/// This copy occurs immediately on the CPU timeline.<br/> | ||||
| 		/// | ||||
| 		/// If you modify this texture in a command buffer and then call this function without calling | ||||
| 		/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/> | ||||
| 		/// | ||||
| 		/// This method forces a sync point and is generally a bad thing to do. | ||||
| 		/// Only use it if you have exhausted all other options.<br/> | ||||
| 		/// | ||||
| 		/// Remember: friends don't let friends readback.<br/> | ||||
| 		/// ⚠️⚠️⚠️ | ||||
| 		/// </summary> | ||||
| 		public void DownloadFromTexture( | ||||
| 			Texture texture, | ||||
| 			TransferBuffer transferBuffer, | ||||
| 			TransferOptions transferOption | ||||
| 		) { | ||||
| 			DownloadFromTexture( | ||||
| 				new TextureRegion(texture), | ||||
| 				transferBuffer, | ||||
| 				new BufferImageCopy(0, 0, 0), | ||||
| 				transferOption | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// ⚠️⚠️⚠️ <br/> | ||||
| 		/// Downloads data from a GpuBuffer to a TransferBuffer. | ||||
| 		/// This copy occurs immediately on the CPU timeline.<br/> | ||||
| 		/// | ||||
| 		/// If you modify this GpuBuffer in a command buffer and then call this function without calling | ||||
| 		/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/> | ||||
| 		/// | ||||
| 		/// This method forces a sync point and is generally a bad thing to do. | ||||
| 		/// Only use it if you have exhausted all other options.<br/> | ||||
| 		/// | ||||
| 		/// Remember: friends don't let friends readback.<br/> | ||||
| 		/// ⚠️⚠️⚠️ | ||||
| 		/// </summary> | ||||
| 		public void DownloadFromBuffer( | ||||
| 			GpuBuffer gpuBuffer, | ||||
| 			TransferBuffer transferBuffer, | ||||
| 			in BufferCopy copyParams, | ||||
| 			TransferOptions transferOption | ||||
| 		) { | ||||
| 			Refresh.Refresh_DownloadFromBuffer( | ||||
| 				Handle, | ||||
| 				gpuBuffer.Handle, | ||||
| 				transferBuffer.Handle, | ||||
| 				copyParams.ToRefresh(), | ||||
| 				(Refresh.TransferOptions) transferOption | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// ⚠️⚠️⚠️ <br/> | ||||
| 		/// Downloads all data in a GpuBuffer to a TransferBuffer. | ||||
| 		/// This copy occurs immediately on the CPU timeline.<br/> | ||||
| 		/// | ||||
| 		/// If you modify this GpuBuffer in a command buffer and then call this function without calling | ||||
| 		/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/> | ||||
| 		/// | ||||
| 		/// This method forces a sync point and is generally a bad thing to do. | ||||
| 		/// Only use it if you have exhausted all other options.<br/> | ||||
| 		/// | ||||
| 		/// Remember: friends don't let friends readback.<br/> | ||||
| 		/// ⚠️⚠️⚠️ | ||||
| 		/// </summary> | ||||
| 		public void DownloadFromBuffer( | ||||
| 			GpuBuffer gpuBuffer, | ||||
| 			TransferBuffer transferBuffer, | ||||
| 			TransferOptions option | ||||
| 		) { | ||||
| 			DownloadFromBuffer( | ||||
| 				gpuBuffer, | ||||
| 				transferBuffer, | ||||
| 				new BufferCopy(0, 0, gpuBuffer.Size), | ||||
| 				option | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		private TextureFormat GetSwapchainFormat(Window window) | ||||
| 		{ | ||||
| 			return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle); | ||||
|  |  | |||
|  | @ -0,0 +1,456 @@ | |||
| using System; | ||||
| using System.IO; | ||||
| using System.Runtime.InteropServices; | ||||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	public static class ImageUtils | ||||
| 	{ | ||||
| 		/// <summary> | ||||
| 		/// Gets pointer to pixel data from compressed image byte data. | ||||
| 		/// | ||||
| 		/// The returned pointer must be freed by calling FreePixelData. | ||||
| 		/// </summary> | ||||
| 		public static unsafe IntPtr GetPixelDataFromBytes( | ||||
| 			Span<byte> data, | ||||
| 			out uint width, | ||||
| 			out uint height, | ||||
| 			out uint sizeInBytes | ||||
| 		) { | ||||
| 			fixed (byte* ptr = data) | ||||
| 			{ | ||||
| 				var pixelData = | ||||
| 					Refresh.Refresh_Image_Load( | ||||
| 					(nint) ptr, | ||||
| 					data.Length, | ||||
| 					out var w, | ||||
| 					out var h, | ||||
| 					out var len | ||||
| 				); | ||||
| 
 | ||||
| 				width = (uint) w; | ||||
| 				height = (uint) h; | ||||
| 				sizeInBytes = (uint) len; | ||||
| 
 | ||||
| 				return pixelData; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Gets pointer to pixel data from a compressed image stream. | ||||
| 		/// | ||||
| 		/// The returned pointer must be freed by calling FreePixelData. | ||||
| 		/// </summary> | ||||
| 		public static unsafe IntPtr GetPixelDataFromStream( | ||||
| 			Stream stream, | ||||
| 			out uint width, | ||||
| 			out uint height, | ||||
| 			out uint sizeInBytes | ||||
| 		) { | ||||
| 			var length = stream.Length; | ||||
| 			var buffer = NativeMemory.Alloc((nuint) length); | ||||
| 			var span = new Span<byte>(buffer, (int) length); | ||||
| 			stream.ReadExactly(span); | ||||
| 
 | ||||
| 			var pixelData = GetPixelDataFromBytes(span, out width, out height, out sizeInBytes); | ||||
| 
 | ||||
| 			NativeMemory.Free(buffer); | ||||
| 
 | ||||
| 			return pixelData; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Gets pointer to pixel data from a compressed image file. | ||||
| 		/// | ||||
| 		/// The returned pointer must be freed by calling FreePixelData. | ||||
| 		/// </summary> | ||||
| 		public static IntPtr GetPixelDataFromFile( | ||||
| 			string path, | ||||
| 			out uint width, | ||||
| 			out uint height, | ||||
| 			out uint sizeInBytes | ||||
| 		) { | ||||
| 			var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); | ||||
| 			return GetPixelDataFromStream(fileStream, out width, out height, out sizeInBytes); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Get metadata from compressed image bytes. | ||||
| 		/// </summary> | ||||
| 		public static unsafe bool ImageInfoFromBytes( | ||||
| 			Span<byte> data, | ||||
| 			out uint width, | ||||
| 			out uint height, | ||||
| 			out uint sizeInBytes | ||||
| 		) { | ||||
| 			fixed (byte* ptr = data) | ||||
| 			{ | ||||
| 				var result = | ||||
| 					Refresh.Refresh_Image_Info( | ||||
| 					(nint) ptr, | ||||
| 					data.Length, | ||||
| 					out var w, | ||||
| 					out var h, | ||||
| 					out var len | ||||
| 				); | ||||
| 
 | ||||
| 				width = (uint) w; | ||||
| 				height = (uint) h; | ||||
| 				sizeInBytes = (uint) len; | ||||
| 
 | ||||
| 				return Conversions.ByteToBool(result); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Get metadata from a compressed image stream. | ||||
| 		/// </summary> | ||||
| 		public static unsafe bool ImageInfoFromStream( | ||||
| 			Stream stream, | ||||
| 			out uint width, | ||||
| 			out uint height, | ||||
| 			out uint sizeInBytes | ||||
| 		) { | ||||
| 			var length = stream.Length; | ||||
| 			var buffer = NativeMemory.Alloc((nuint) length); | ||||
| 			var span = new Span<byte>(buffer, (int) length); | ||||
| 			stream.ReadExactly(span); | ||||
| 
 | ||||
| 			var result = ImageInfoFromBytes(span, out width, out height, out sizeInBytes); | ||||
| 
 | ||||
| 			NativeMemory.Free(buffer); | ||||
| 
 | ||||
| 			return result; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Get metadata from a compressed image file. | ||||
| 		/// </summary> | ||||
| 		public static bool ImageInfoFromFile( | ||||
| 			string path, | ||||
| 			out uint width, | ||||
| 			out uint height, | ||||
| 			out uint sizeInBytes | ||||
| 		) { | ||||
| 			var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); | ||||
| 			return ImageInfoFromStream(fileStream, out width, out height, out sizeInBytes); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Frees pixel data obtained from GetPixelData methods. | ||||
| 		/// </summary> | ||||
| 		public static void FreePixelData(IntPtr pixels) | ||||
| 		{ | ||||
| 			Refresh.Refresh_Image_Free(pixels); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Saves pixel data contained in a TransferBuffer to a PNG file. | ||||
| 		/// </summary> | ||||
| 		public static unsafe void SavePNG( | ||||
| 			string path, | ||||
| 			TransferBuffer transferBuffer, | ||||
| 			uint bufferOffsetInBytes, | ||||
| 			int width, | ||||
| 			int height, | ||||
| 			bool bgra | ||||
| 		) { | ||||
| 			var sizeInBytes = width * height * 4; | ||||
| 
 | ||||
| 			var pixelsPtr = NativeMemory.Alloc((nuint) sizeInBytes); | ||||
| 			var pixelsSpan = new Span<byte>(pixelsPtr, sizeInBytes); | ||||
| 
 | ||||
| 			transferBuffer.GetData(pixelsSpan, bufferOffsetInBytes); | ||||
| 
 | ||||
| 			if (bgra) | ||||
| 			{ | ||||
| 				// if data is bgra, we have to swap the R and B channels | ||||
| 				var rgbaPtr = NativeMemory.Alloc((nuint) sizeInBytes); | ||||
| 				var rgbaSpan = new Span<byte>(rgbaPtr, sizeInBytes); | ||||
| 
 | ||||
| 				for (var i = 0; i < sizeInBytes; i += 4) | ||||
| 				{ | ||||
| 					rgbaSpan[i] = pixelsSpan[i + 2]; | ||||
| 					rgbaSpan[i + 1] = pixelsSpan[i + 1]; | ||||
| 					rgbaSpan[i + 2] = pixelsSpan[i]; | ||||
| 					rgbaSpan[i + 3] = pixelsSpan[i + 3]; | ||||
| 				} | ||||
| 
 | ||||
| 				NativeMemory.Free(pixelsPtr); | ||||
| 				pixelsPtr = rgbaPtr; | ||||
| 			} | ||||
| 
 | ||||
| 			Refresh.Refresh_Image_SavePNG(path, (nint) pixelsPtr, width, height); | ||||
| 			NativeMemory.Free(pixelsPtr); | ||||
| 		} | ||||
| 
 | ||||
| 		// DDS loading extension, based on MojoDDS | ||||
| 		// Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194 | ||||
| 		public static void ParseDDS( | ||||
| 			BinaryReader reader, | ||||
| 			out TextureFormat format, | ||||
| 			out int width, | ||||
| 			out int height, | ||||
| 			out int levels, | ||||
| 			out bool isCube | ||||
| 		) { | ||||
| 			// A whole bunch of magic numbers, yay DDS! | ||||
| 			const uint DDS_MAGIC = 0x20534444; | ||||
| 			const uint DDS_HEADERSIZE = 124; | ||||
| 			const uint DDS_PIXFMTSIZE = 32; | ||||
| 			const uint DDSD_HEIGHT = 0x2; | ||||
| 			const uint DDSD_WIDTH = 0x4; | ||||
| 			const uint DDSD_PITCH = 0x8; | ||||
| 			const uint DDSD_LINEARSIZE = 0x80000; | ||||
| 			const uint DDSD_REQ = ( | ||||
| 				DDSD_HEIGHT | DDSD_WIDTH | ||||
| 			); | ||||
| 			const uint DDSCAPS_MIPMAP = 0x400000; | ||||
| 			const uint DDSCAPS_TEXTURE = 0x1000; | ||||
| 			const uint DDSCAPS2_CUBEMAP = 0x200; | ||||
| 			const uint DDPF_FOURCC = 0x4; | ||||
| 			const uint DDPF_RGB = 0x40; | ||||
| 			const uint FOURCC_DXT1 = 0x31545844; | ||||
| 			const uint FOURCC_DXT3 = 0x33545844; | ||||
| 			const uint FOURCC_DXT5 = 0x35545844; | ||||
| 			const uint FOURCC_DX10 = 0x30315844; | ||||
| 			const uint pitchAndLinear = ( | ||||
| 				DDSD_PITCH | DDSD_LINEARSIZE | ||||
| 			); | ||||
| 
 | ||||
| 			// File should start with 'DDS ' | ||||
| 			if (reader.ReadUInt32() != DDS_MAGIC) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Not a DDS!"); | ||||
| 			} | ||||
| 
 | ||||
| 			// Texture info | ||||
| 			uint size = reader.ReadUInt32(); | ||||
| 			if (size != DDS_HEADERSIZE) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Invalid DDS header!"); | ||||
| 			} | ||||
| 			uint flags = reader.ReadUInt32(); | ||||
| 			if ((flags & DDSD_REQ) != DDSD_REQ) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Invalid DDS flags!"); | ||||
| 			} | ||||
| 			if ((flags & pitchAndLinear) == pitchAndLinear) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Invalid DDS flags!"); | ||||
| 			} | ||||
| 			height = reader.ReadInt32(); | ||||
| 			width = reader.ReadInt32(); | ||||
| 			reader.ReadUInt32(); // dwPitchOrLinearSize, unused | ||||
| 			reader.ReadUInt32(); // dwDepth, unused | ||||
| 			levels = reader.ReadInt32(); | ||||
| 
 | ||||
| 			// "Reserved" | ||||
| 			reader.ReadBytes(4 * 11); | ||||
| 
 | ||||
| 			// Format info | ||||
| 			uint formatSize = reader.ReadUInt32(); | ||||
| 			if (formatSize != DDS_PIXFMTSIZE) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Bogus PIXFMTSIZE!"); | ||||
| 			} | ||||
| 			uint formatFlags = reader.ReadUInt32(); | ||||
| 			uint formatFourCC = reader.ReadUInt32(); | ||||
| 			uint formatRGBBitCount = reader.ReadUInt32(); | ||||
| 			uint formatRBitMask = reader.ReadUInt32(); | ||||
| 			uint formatGBitMask = reader.ReadUInt32(); | ||||
| 			uint formatBBitMask = reader.ReadUInt32(); | ||||
| 			uint formatABitMask = reader.ReadUInt32(); | ||||
| 
 | ||||
| 			// dwCaps "stuff" | ||||
| 			uint caps = reader.ReadUInt32(); | ||||
| 			if ((caps & DDSCAPS_TEXTURE) == 0) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Not a texture!"); | ||||
| 			} | ||||
| 
 | ||||
| 			isCube = false; | ||||
| 
 | ||||
| 			uint caps2 = reader.ReadUInt32(); | ||||
| 			if (caps2 != 0) | ||||
| 			{ | ||||
| 				if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP) | ||||
| 				{ | ||||
| 					isCube = true; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					throw new NotSupportedException("Invalid caps2!"); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			reader.ReadUInt32(); // dwCaps3, unused | ||||
| 			reader.ReadUInt32(); // dwCaps4, unused | ||||
| 
 | ||||
| 			// "Reserved" | ||||
| 			reader.ReadUInt32(); | ||||
| 
 | ||||
| 			// Mipmap sanity check | ||||
| 			if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP) | ||||
| 			{ | ||||
| 				levels = 1; | ||||
| 			} | ||||
| 
 | ||||
| 			// Determine texture format | ||||
| 			if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC) | ||||
| 			{ | ||||
| 				switch (formatFourCC) | ||||
| 				{ | ||||
| 					case 0x71: // D3DFMT_A16B16G16R16F | ||||
| 						format = TextureFormat.R16G16B16A16_SFLOAT; | ||||
| 						break; | ||||
| 					case 0x74: // D3DFMT_A32B32G32R32F | ||||
| 						format = TextureFormat.R32G32B32A32_SFLOAT; | ||||
| 						break; | ||||
| 					case FOURCC_DXT1: | ||||
| 						format = TextureFormat.BC1; | ||||
| 						break; | ||||
| 					case FOURCC_DXT3: | ||||
| 						format = TextureFormat.BC2; | ||||
| 						break; | ||||
| 					case FOURCC_DXT5: | ||||
| 						format = TextureFormat.BC3; | ||||
| 						break; | ||||
| 					case FOURCC_DX10: | ||||
| 						// If the fourCC is DX10, there is an extra header with additional format information. | ||||
| 						uint dxgiFormat = reader.ReadUInt32(); | ||||
| 
 | ||||
| 						// These values are taken from the DXGI_FORMAT enum. | ||||
| 						switch (dxgiFormat) | ||||
| 						{ | ||||
| 							case 2: | ||||
| 								format = TextureFormat.R32G32B32A32_SFLOAT; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 10: | ||||
| 								format = TextureFormat.R16G16B16A16_SFLOAT; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 71: | ||||
| 								format = TextureFormat.BC1; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 74: | ||||
| 								format = TextureFormat.BC2; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 77: | ||||
| 								format = TextureFormat.BC3; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 98: | ||||
| 								format = TextureFormat.BC7; | ||||
| 								break; | ||||
| 
 | ||||
| 							default: | ||||
| 								throw new NotSupportedException( | ||||
| 									"Unsupported DDS texture format" | ||||
| 								); | ||||
| 						} | ||||
| 
 | ||||
| 						uint resourceDimension = reader.ReadUInt32(); | ||||
| 
 | ||||
| 						// These values are taken from the D3D10_RESOURCE_DIMENSION enum. | ||||
| 						switch (resourceDimension) | ||||
| 						{ | ||||
| 							case 0: // Unknown | ||||
| 							case 1: // Buffer | ||||
| 								throw new NotSupportedException( | ||||
| 									"Unsupported DDS texture format" | ||||
| 								); | ||||
| 							default: | ||||
| 								break; | ||||
| 						} | ||||
| 
 | ||||
| 						/* | ||||
| 						 * This flag seemingly only indicates if the texture is a cube map. | ||||
| 						 * This is already determined above. Cool! | ||||
| 						 */ | ||||
| 						uint miscFlag = reader.ReadUInt32(); | ||||
| 
 | ||||
| 						/* | ||||
| 						 * Indicates the number of elements in the texture array. | ||||
| 						 * We don't support texture arrays so just throw if it's greater than 1. | ||||
| 						 */ | ||||
| 						uint arraySize = reader.ReadUInt32(); | ||||
| 
 | ||||
| 						if (arraySize > 1) | ||||
| 						{ | ||||
| 							throw new NotSupportedException( | ||||
| 								"Unsupported DDS texture format" | ||||
| 							); | ||||
| 						} | ||||
| 
 | ||||
| 						reader.ReadUInt32(); // reserved | ||||
| 
 | ||||
| 						break; | ||||
| 					default: | ||||
| 						throw new NotSupportedException( | ||||
| 							"Unsupported DDS texture format" | ||||
| 						); | ||||
| 				} | ||||
| 			} | ||||
| 			else if ((formatFlags & DDPF_RGB) == DDPF_RGB) | ||||
| 			{ | ||||
| 				if (	formatRGBBitCount != 32 || | ||||
| 					formatRBitMask != 0x00FF0000 || | ||||
| 					formatGBitMask != 0x0000FF00 || | ||||
| 					formatBBitMask != 0x000000FF || | ||||
| 					formatABitMask != 0xFF000000	) | ||||
| 				{ | ||||
| 					throw new NotSupportedException( | ||||
| 						"Unsupported DDS texture format" | ||||
| 					); | ||||
| 				} | ||||
| 
 | ||||
| 				format = TextureFormat.B8G8R8A8; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				throw new NotSupportedException( | ||||
| 					"Unsupported DDS texture format" | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public static int CalculateDDSLevelSize( | ||||
| 			int width, | ||||
| 			int height, | ||||
| 			TextureFormat format | ||||
| 		) { | ||||
| 			if (format == TextureFormat.R8G8B8A8) | ||||
| 			{ | ||||
| 				return (((width * 32) + 7) / 8) * height; | ||||
| 			} | ||||
| 			else if (format == TextureFormat.R16G16B16A16_SFLOAT) | ||||
| 			{ | ||||
| 				return (((width * 64) + 7) / 8) * height; | ||||
| 			} | ||||
| 			else if (format == TextureFormat.R32G32B32A32_SFLOAT) | ||||
| 			{ | ||||
| 				return (((width * 128) + 7) / 8) * height; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				int blockSize = 16; | ||||
| 				if (format == TextureFormat.BC1) | ||||
| 				{ | ||||
| 					blockSize = 8; | ||||
| 				} | ||||
| 				width = System.Math.Max(width, 1); | ||||
| 				height = System.Math.Max(height, 1); | ||||
| 				return ( | ||||
| 					((width + 3) / 4) * | ||||
| 					((height + 3) / 4) * | ||||
| 					blockSize | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -297,10 +297,29 @@ namespace MoonWorks.Graphics | |||
| 		IntOpaqueWhite | ||||
| 	} | ||||
| 
 | ||||
| 	public enum TransferUsage | ||||
| 	{ | ||||
| 		Buffer, | ||||
| 		Texture | ||||
| 	} | ||||
| 
 | ||||
| 	public enum TransferOptions | ||||
| 	{ | ||||
| 		Cycle, | ||||
| 		Unsafe | ||||
| 	} | ||||
| 
 | ||||
| 	public enum WriteOptions | ||||
| 	{ | ||||
| 		Cycle, | ||||
| 		Unsafe, | ||||
| 		Safe | ||||
| 	} | ||||
| 
 | ||||
| 	public enum Backend | ||||
| 	{ | ||||
| 		DontCare, | ||||
| 		Vulkan, | ||||
| 		D3D11, | ||||
| 		PS5, | ||||
| 		Invalid | ||||
| 	} | ||||
|  |  | |||
|  | @ -154,11 +154,7 @@ namespace MoonWorks.Graphics | |||
| 		public StencilOp PassOp; | ||||
| 		public StencilOp DepthFailOp; | ||||
| 		public CompareOp CompareOp; | ||||
| 		public uint CompareMask; | ||||
| 		public uint WriteMask; | ||||
| 		public uint Reference; | ||||
| 
 | ||||
| 		// FIXME: can we do an explicit cast here? | ||||
| 		public Refresh.StencilOpState ToRefresh() | ||||
| 		{ | ||||
| 			return new Refresh.StencilOpState | ||||
|  | @ -166,61 +162,97 @@ namespace MoonWorks.Graphics | |||
| 				failOp = (Refresh.StencilOp) FailOp, | ||||
| 				passOp = (Refresh.StencilOp) PassOp, | ||||
| 				depthFailOp = (Refresh.StencilOp) DepthFailOp, | ||||
| 				compareOp = (Refresh.CompareOp) CompareOp, | ||||
| 				compareMask = CompareMask, | ||||
| 				writeMask = WriteMask, | ||||
| 				reference = Reference | ||||
| 				compareOp = (Refresh.CompareOp) CompareOp | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	[StructLayout(LayoutKind.Sequential)] | ||||
| 	/// <summary> | ||||
| 	/// Determines how a color texture will be read/written in a render pass. | ||||
| 	/// </summary> | ||||
| 	public struct ColorAttachmentInfo | ||||
| 	{ | ||||
| 		public Texture Texture; | ||||
| 		public uint Depth; | ||||
| 		public uint Layer; | ||||
| 		public uint Level; | ||||
| 		public TextureSlice TextureSlice; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// If LoadOp is set to Clear, the texture slice will be cleared to this color. | ||||
| 		/// </summary> | ||||
| 		public Color ClearColor; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Determines what is done with the texture slice memory | ||||
| 		/// at the beginning of the render pass. <br/> | ||||
| 		/// | ||||
| 		///   Load: | ||||
| 		///     Loads the data currently in the texture slice. <br/> | ||||
| 		/// | ||||
| 		///   Clear: | ||||
| 		///     Clears the texture slice to a single color. <br/> | ||||
| 		/// | ||||
| 		///   DontCare: | ||||
| 		///     The driver will do whatever it wants with the texture slice data. | ||||
| 		///     This is a good option if you know that every single pixel will be written in the render pass. | ||||
| 		/// </summary> | ||||
| 		public LoadOp LoadOp; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Determines what is done with the texture slice memory | ||||
| 		/// at the end of the render pass. <br/> | ||||
| 		/// | ||||
| 		///   Store: | ||||
| 		///     Stores the results of the render pass in the texture slice memory. <br/> | ||||
| 		/// | ||||
| 		///   DontCare: | ||||
| 		///     The driver will do whatever it wants with the texture slice memory. | ||||
| 		/// </summary> | ||||
| 		public StoreOp StoreOp; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Specifies data dependency behavior. This option is ignored if LoadOp is Load. <br/> | ||||
| 		/// | ||||
| 		///   Cycle: | ||||
| 		///     If this texture slice has been used in commands that have not completed, | ||||
| 		///     the implementation may prevent a dependency on those commands | ||||
| 		///     at the cost of increased memory usage. | ||||
| 		///     You may NOT assume that any of the previous texture (not slice!) data is retained. | ||||
| 		///     This may prevent stalls when frequently reusing a texture slice in rendering. <br/> | ||||
| 		/// | ||||
| 		///   SafeOverwrite: | ||||
| 		///     Overwrites the data safely using a GPU memory barrier. | ||||
| 		/// </summary> | ||||
| 		public WriteOptions WriteOption; | ||||
| 
 | ||||
| 		public ColorAttachmentInfo( | ||||
| 			Texture texture, | ||||
| 			TextureSlice textureSlice, | ||||
| 			WriteOptions writeOption, | ||||
| 			Color clearColor, | ||||
| 			StoreOp storeOp = StoreOp.Store | ||||
| 		) { | ||||
| 			Texture = texture; | ||||
| 			Depth = 0; | ||||
| 			Layer = 0; | ||||
| 			Level = 0; | ||||
| 			TextureSlice = textureSlice; | ||||
| 			ClearColor = clearColor; | ||||
| 			LoadOp = LoadOp.Clear; | ||||
| 			StoreOp = storeOp; | ||||
| 			WriteOption = writeOption; | ||||
| 		} | ||||
| 
 | ||||
| 		public ColorAttachmentInfo( | ||||
| 			Texture texture, | ||||
| 			TextureSlice textureSlice, | ||||
| 			WriteOptions writeOption, | ||||
| 			LoadOp loadOp = LoadOp.DontCare, | ||||
| 			StoreOp storeOp = StoreOp.Store | ||||
| 		) { | ||||
| 			Texture = texture; | ||||
| 			Depth = 0; | ||||
| 			Layer = 0; | ||||
| 			Level = 0; | ||||
| 			TextureSlice = textureSlice; | ||||
| 			ClearColor = Color.White; | ||||
| 			LoadOp = loadOp; | ||||
| 			StoreOp = storeOp; | ||||
| 			WriteOption = writeOption; | ||||
| 		} | ||||
| 
 | ||||
| 		public Refresh.ColorAttachmentInfo ToRefresh() | ||||
| 		{ | ||||
| 			return new Refresh.ColorAttachmentInfo | ||||
| 			{ | ||||
| 				texture = Texture.Handle, | ||||
| 				depth = Depth, | ||||
| 				layer = Layer, | ||||
| 				level = Level, | ||||
| 				textureSlice = TextureSlice.ToRefreshTextureSlice(), | ||||
| 				clearColor = new Refresh.Vec4 | ||||
| 				{ | ||||
| 					x = ClearColor.R / 255f, | ||||
|  | @ -229,92 +261,142 @@ namespace MoonWorks.Graphics | |||
| 					w = ClearColor.A / 255f | ||||
| 				}, | ||||
| 				loadOp = (Refresh.LoadOp) LoadOp, | ||||
| 				storeOp = (Refresh.StoreOp) StoreOp | ||||
| 				storeOp = (Refresh.StoreOp) StoreOp, | ||||
| 				writeOption = (Refresh.WriteOptions) WriteOption | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	[StructLayout(LayoutKind.Sequential)] | ||||
| 	/// <summary> | ||||
| 	/// Determines how a depth/stencil texture will be read/written in a render pass. | ||||
| 	/// </summary> | ||||
| 	public struct DepthStencilAttachmentInfo | ||||
| 	{ | ||||
| 		public Texture Texture; | ||||
| 		public uint Depth; | ||||
| 		public uint Layer; | ||||
| 		public uint Level; | ||||
| 		public TextureSlice TextureSlice; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// If LoadOp is set to Clear, the texture slice depth will be cleared to this depth value. <br/> | ||||
| 		/// If StencilLoadOp is set to Clear, the texture slice stencil value will be cleared to this stencil value. | ||||
| 		/// </summary> | ||||
| 		public DepthStencilValue DepthStencilClearValue; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Determines what is done with the texture slice depth values | ||||
| 		/// at the beginning of the render pass. <br/> | ||||
| 		/// | ||||
| 		///   Load: | ||||
| 		///     Loads the data currently in the texture slice. <br/> | ||||
| 		/// | ||||
| 		///   Clear: | ||||
| 		///     Clears the texture slice to a single depth value. <br/> | ||||
| 		/// | ||||
| 		///   DontCare: | ||||
| 		///     The driver will do whatever it wants with the texture slice data. | ||||
| 		///     This is a good option if you know that every single pixel will be written in the render pass. | ||||
| 		/// </summary> | ||||
| 		public LoadOp LoadOp; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Determines what is done with the texture slice depth values | ||||
| 		/// at the end of the render pass. <br/> | ||||
| 		/// | ||||
| 		///   Store: | ||||
| 		///     Stores the results of the render pass in the texture slice memory. <br/> | ||||
| 		/// | ||||
| 		///   DontCare: | ||||
| 		///     The driver will do whatever it wants with the texture slice memory. | ||||
| 		///     This is usually a good option for depth textures that don't need to be reused. | ||||
| 		/// </summary> | ||||
| 		public StoreOp StoreOp; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Determines what is done with the texture slice stencil values | ||||
| 		/// at the beginning of the render pass. <br/> | ||||
| 		/// | ||||
| 		///   Load: | ||||
| 		///     Loads the data currently in the texture slice. <br/> | ||||
| 		/// | ||||
| 		///   Clear: | ||||
| 		///     Clears the texture slice to a single stencil value. <br/> | ||||
| 		/// | ||||
| 		///   DontCare: | ||||
| 		///     The driver will do whatever it wants with the texture slice data. | ||||
| 		///     This is a good option if you know that every single pixel will be written in the render pass. | ||||
| 		/// </summary> | ||||
| 		public LoadOp StencilLoadOp; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Determines what is done with the texture slice stencil values | ||||
| 		/// at the end of the render pass. <br/> | ||||
| 		/// | ||||
| 		///   Store: | ||||
| 		///     Stores the results of the render pass in the texture slice memory. <br/> | ||||
| 		/// | ||||
| 		///   DontCare: | ||||
| 		///     The driver will do whatever it wants with the texture slice memory. | ||||
| 		///     This is usually a good option for stencil textures that don't need to be reused. | ||||
| 		/// </summary> | ||||
| 		public StoreOp StencilStoreOp; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Specifies data dependency behavior. This option is ignored if LoadOp or StencilLoadOp is Load. <br/> | ||||
| 		/// | ||||
| 		///   Cycle: | ||||
| 		///     If this texture slice has been used in commands that have not completed, | ||||
| 		///     the implementation may prevent a dependency on those commands | ||||
| 		///     at the cost of increased memory usage. | ||||
| 		///     You may NOT assume that any of the previous texture (not slice!) data is retained. | ||||
| 		///     This may prevent stalls when frequently reusing a texture slice in rendering. <br/> | ||||
| 		/// | ||||
| 		///   SafeOverwrite: | ||||
| 		///     Overwrites the data safely using a GPU memory barrier. | ||||
| 		/// </summary> | ||||
| 		public WriteOptions WriteOption; | ||||
| 
 | ||||
| 		public DepthStencilAttachmentInfo( | ||||
| 			Texture texture, | ||||
| 			TextureSlice textureSlice, | ||||
| 			WriteOptions writeOption, | ||||
| 			DepthStencilValue clearValue, | ||||
| 			StoreOp depthStoreOp = StoreOp.Store, | ||||
| 			StoreOp stencilStoreOp = StoreOp.Store | ||||
| 		) | ||||
| 		{ | ||||
| 			Texture = texture; | ||||
| 			Depth = 0; | ||||
| 			Layer = 0; | ||||
| 			Level = 0; | ||||
| 			StoreOp depthStoreOp = StoreOp.DontCare, | ||||
| 			StoreOp stencilStoreOp = StoreOp.DontCare | ||||
| 		){ | ||||
| 			TextureSlice = textureSlice; | ||||
| 			DepthStencilClearValue = clearValue; | ||||
| 			LoadOp = LoadOp.Clear; | ||||
| 			StoreOp = depthStoreOp; | ||||
| 			StencilLoadOp = LoadOp.Clear; | ||||
| 			StencilStoreOp = stencilStoreOp; | ||||
| 			WriteOption = writeOption; | ||||
| 		} | ||||
| 
 | ||||
| 		public DepthStencilAttachmentInfo( | ||||
| 			Texture texture, | ||||
| 			TextureSlice textureSlice, | ||||
| 			WriteOptions writeOption, | ||||
| 			LoadOp loadOp = LoadOp.DontCare, | ||||
| 			StoreOp storeOp = StoreOp.Store, | ||||
| 			StoreOp storeOp = StoreOp.DontCare, | ||||
| 			LoadOp stencilLoadOp = LoadOp.DontCare, | ||||
| 			StoreOp stencilStoreOp = StoreOp.Store | ||||
| 			StoreOp stencilStoreOp = StoreOp.DontCare | ||||
| 		) { | ||||
| 			Texture = texture; | ||||
| 			Depth = 0; | ||||
| 			Layer = 0; | ||||
| 			Level = 0; | ||||
| 			TextureSlice = textureSlice; | ||||
| 			DepthStencilClearValue = new DepthStencilValue(); | ||||
| 			LoadOp = loadOp; | ||||
| 			StoreOp = storeOp; | ||||
| 			StencilLoadOp = stencilLoadOp; | ||||
| 			StencilStoreOp = stencilStoreOp; | ||||
| 		} | ||||
| 
 | ||||
| 		public DepthStencilAttachmentInfo( | ||||
| 			Texture texture, | ||||
| 			DepthStencilValue depthStencilValue, | ||||
| 			LoadOp loadOp, | ||||
| 			StoreOp storeOp, | ||||
| 			LoadOp stencilLoadOp, | ||||
| 			StoreOp stencilStoreOp | ||||
| 		) { | ||||
| 			Texture = texture; | ||||
| 			Depth = 0; | ||||
| 			Layer = 0; | ||||
| 			Level = 0; | ||||
| 			DepthStencilClearValue = depthStencilValue; | ||||
| 			LoadOp = loadOp; | ||||
| 			StoreOp = storeOp; | ||||
| 			StencilLoadOp = stencilLoadOp; | ||||
| 			StencilStoreOp = stencilStoreOp; | ||||
| 			WriteOption = writeOption; | ||||
| 		} | ||||
| 
 | ||||
| 		public Refresh.DepthStencilAttachmentInfo ToRefresh() | ||||
| 		{ | ||||
| 			return new Refresh.DepthStencilAttachmentInfo | ||||
| 			{ | ||||
| 				texture = Texture.Handle, | ||||
| 				depth = Depth, | ||||
| 				layer = Layer, | ||||
| 				level = Level, | ||||
| 				textureSlice = TextureSlice.ToRefreshTextureSlice(), | ||||
| 				depthStencilClearValue = DepthStencilClearValue.ToRefresh(), | ||||
| 				loadOp = (Refresh.LoadOp) LoadOp, | ||||
| 				storeOp = (Refresh.StoreOp) StoreOp, | ||||
| 				stencilLoadOp = (Refresh.LoadOp) StencilLoadOp, | ||||
| 				stencilStoreOp = (Refresh.StoreOp) StencilStoreOp | ||||
| 				stencilStoreOp = (Refresh.StoreOp) StencilStoreOp, | ||||
| 				writeOption = (Refresh.WriteOptions) WriteOption | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
|  | @ -354,4 +436,55 @@ namespace MoonWorks.Graphics | |||
| 			FirstInstance = firstInstance; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	[StructLayout(LayoutKind.Sequential)] | ||||
| 	public struct BufferCopy | ||||
| 	{ | ||||
| 		public uint SrcOffset; | ||||
| 		public uint DstOffset; | ||||
| 		public uint Size; | ||||
| 
 | ||||
| 		public BufferCopy( | ||||
| 			uint srcOffset, | ||||
| 			uint dstOffset, | ||||
| 			uint size | ||||
| 		) { | ||||
| 			SrcOffset = srcOffset; | ||||
| 			DstOffset = dstOffset; | ||||
| 			Size = size; | ||||
| 		} | ||||
| 
 | ||||
| 		public Refresh.BufferCopy ToRefresh() | ||||
| 		{ | ||||
| 			return new Refresh.BufferCopy | ||||
| 			{ | ||||
| 				srcOffset = SrcOffset, | ||||
| 				dstOffset = DstOffset, | ||||
| 				size = Size | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// <summary> | ||||
| 	/// Parameters for a copy between buffer and image. | ||||
| 	/// </summary> | ||||
| 	/// <param name="BufferOffset">The offset into the buffer.</param> | ||||
| 	/// <param name="BufferStride">If 0, image data is assumed tightly packed.</param> | ||||
| 	/// <param name="BufferImageHeight">If 0, image data is assumed tightly packed.</param> | ||||
| 	[StructLayout(LayoutKind.Sequential)] | ||||
| 	public readonly record struct BufferImageCopy( | ||||
| 		uint BufferOffset, | ||||
| 		uint BufferStride, | ||||
| 		uint BufferImageHeight | ||||
| 	) { | ||||
| 		public Refresh.BufferImageCopy ToRefresh() | ||||
| 		{ | ||||
| 			return new Refresh.BufferImageCopy | ||||
| 			{ | ||||
| 				bufferOffset = BufferOffset, | ||||
| 				bufferStride = BufferStride, | ||||
| 				bufferImageHeight = BufferImageHeight | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,381 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Runtime.InteropServices; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// A convenience structure for creating resources and uploading their data. | ||||
| 	/// | ||||
| 	/// Note that Upload or UploadAndWait must be called after the Create methods for the data to actually be uploaded. | ||||
| 	/// | ||||
| 	/// Note that this structure does not magically keep memory usage down - | ||||
| 	/// you may want to stagger uploads over multiple submissions to minimize memory usage. | ||||
| 	/// </summary> | ||||
| 	public unsafe class ResourceUploader : GraphicsResource | ||||
| 	{ | ||||
| 		TransferBuffer BufferTransferBuffer; | ||||
| 		TransferBuffer TextureTransferBuffer; | ||||
| 
 | ||||
| 		byte* bufferData; | ||||
| 		uint bufferDataOffset = 0; | ||||
| 		uint bufferDataSize = 1024; | ||||
| 
 | ||||
| 		byte* textureData; | ||||
| 		uint textureDataOffset = 0; | ||||
| 		uint textureDataSize = 1024; | ||||
| 
 | ||||
| 		List<(GpuBuffer, BufferCopy, WriteOptions)> BufferUploads = new List<(GpuBuffer, BufferCopy, WriteOptions)>(); | ||||
| 		List<(TextureRegion, uint, WriteOptions)> TextureUploads = new List<(TextureRegion, uint, WriteOptions)>(); | ||||
| 
 | ||||
| 		public ResourceUploader(GraphicsDevice device) : base(device) | ||||
| 		{ | ||||
| 			bufferData = (byte*) NativeMemory.Alloc(bufferDataSize); | ||||
| 			textureData = (byte*) NativeMemory.Alloc(textureDataSize); | ||||
| 		} | ||||
| 
 | ||||
| 		// Buffers | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a GpuBuffer with data to be uploaded. | ||||
| 		/// </summary> | ||||
| 		public GpuBuffer CreateBuffer<T>(Span<T> data, BufferUsageFlags usageFlags) where T : unmanaged | ||||
| 		{ | ||||
| 			var lengthInBytes = (uint) (Marshal.SizeOf<T>() * data.Length); | ||||
| 			var gpuBuffer = new GpuBuffer(Device, usageFlags, lengthInBytes); | ||||
| 
 | ||||
| 			SetBufferData(gpuBuffer, 0, data, WriteOptions.Unsafe); | ||||
| 
 | ||||
| 			return gpuBuffer; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Prepares upload of data into a GpuBuffer. | ||||
| 		/// </summary> | ||||
| 		public void SetBufferData<T>(GpuBuffer buffer, uint bufferOffsetInElements, Span<T> data, WriteOptions option) where T : unmanaged | ||||
| 		{ | ||||
| 			uint elementSize = (uint) Marshal.SizeOf<T>(); | ||||
| 			uint offsetInBytes = elementSize * bufferOffsetInElements; | ||||
| 			uint lengthInBytes = (uint) (elementSize * data.Length); | ||||
| 
 | ||||
| 			uint resourceOffset; | ||||
| 			fixed (void* spanPtr = data) | ||||
| 			{ | ||||
| 				resourceOffset = CopyBufferData(spanPtr, lengthInBytes); | ||||
| 			} | ||||
| 
 | ||||
| 			var bufferCopyParams = new BufferCopy(resourceOffset, offsetInBytes, lengthInBytes); | ||||
| 			BufferUploads.Add((buffer, bufferCopyParams, option)); | ||||
| 		} | ||||
| 
 | ||||
| 		// Textures | ||||
| 
 | ||||
| 		public Texture CreateTexture2D<T>(Span<T> pixelData, uint width, uint height) where T : unmanaged | ||||
| 		{ | ||||
| 			var texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler); | ||||
| 			SetTextureData(texture, pixelData, WriteOptions.Unsafe); | ||||
| 			return texture; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 2D Texture from compressed image data to be uploaded. | ||||
| 		/// </summary> | ||||
| 		public Texture CreateTexture2DFromCompressed(Span<byte> compressedImageData) | ||||
| 		{ | ||||
| 			ImageUtils.ImageInfoFromBytes(compressedImageData, out var width, out var height, out var _); | ||||
| 			var texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler); | ||||
| 			SetTextureDataFromCompressed(texture, compressedImageData); | ||||
| 			return texture; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 2D Texture from a compressed image stream to be uploaded. | ||||
| 		/// </summary> | ||||
| 		public Texture CreateTexture2DFromCompressed(Stream compressedImageStream) | ||||
| 		{ | ||||
| 			var length = compressedImageStream.Length; | ||||
| 			var buffer = NativeMemory.Alloc((nuint) length); | ||||
| 			var span = new Span<byte>(buffer, (int) length); | ||||
| 			compressedImageStream.ReadExactly(span); | ||||
| 
 | ||||
| 			var texture = CreateTexture2DFromCompressed(span); | ||||
| 
 | ||||
| 			NativeMemory.Free(buffer); | ||||
| 
 | ||||
| 			return texture; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 2D Texture from a compressed image file to be uploaded. | ||||
| 		/// </summary> | ||||
| 		public Texture CreateTexture2DFromCompressed(string compressedImageFilePath) | ||||
| 		{ | ||||
| 			var fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read); | ||||
| 			return CreateTexture2DFromCompressed(fileStream); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a texture from a DDS stream. | ||||
| 		/// </summary> | ||||
| 		public Texture CreateTextureFromDDS(Stream stream) | ||||
| 		{ | ||||
| 			using var reader = new BinaryReader(stream); | ||||
| 			Texture texture; | ||||
| 			int faces; | ||||
| 			ImageUtils.ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube); | ||||
| 
 | ||||
| 			if (isCube) | ||||
| 			{ | ||||
| 				texture = Texture.CreateTextureCube(Device, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels); | ||||
| 				faces = 6; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				texture = Texture.CreateTexture2D(Device, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels); | ||||
| 				faces = 1; | ||||
| 			} | ||||
| 
 | ||||
| 			for (int face = 0; face < faces; face += 1) | ||||
| 			{ | ||||
| 				for (int level = 0; level < levels; level += 1) | ||||
| 				{ | ||||
| 					var levelWidth = width >> level; | ||||
| 					var levelHeight = height >> level; | ||||
| 
 | ||||
| 					var levelSize = ImageUtils.CalculateDDSLevelSize(levelWidth, levelHeight, format); | ||||
| 					var byteBuffer = NativeMemory.Alloc((nuint) levelSize); | ||||
| 					var byteSpan = new Span<byte>(byteBuffer, levelSize); | ||||
| 					stream.ReadExactly(byteSpan); | ||||
| 
 | ||||
| 					var textureRegion = new TextureRegion | ||||
| 					{ | ||||
| 						TextureSlice = new TextureSlice | ||||
| 						{ | ||||
| 							Texture = texture, | ||||
| 							Layer = (uint) face, | ||||
| 							MipLevel = (uint) level | ||||
| 						}, | ||||
| 						X = 0, | ||||
| 						Y = 0, | ||||
| 						Z = 0, | ||||
| 						Width = (uint) levelWidth, | ||||
| 						Height = (uint) levelHeight, | ||||
| 						Depth = 1 | ||||
| 					}; | ||||
| 
 | ||||
| 					SetTextureData(textureRegion, byteSpan, WriteOptions.Unsafe); | ||||
| 
 | ||||
| 					NativeMemory.Free(byteBuffer); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			return texture; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a texture from a DDS file. | ||||
| 		/// </summary> | ||||
| 		public Texture CreateTextureFromDDS(string path) | ||||
| 		{ | ||||
| 			var stream = new FileStream(path, FileMode.Open, FileAccess.Read); | ||||
| 			return CreateTextureFromDDS(stream); | ||||
| 		} | ||||
| 
 | ||||
| 		public void SetTextureDataFromCompressed(TextureRegion textureRegion, Span<byte> compressedImageData) | ||||
| 		{ | ||||
| 			var pixelData = ImageUtils.GetPixelDataFromBytes(compressedImageData, out var _, out var _, out var sizeInBytes); | ||||
| 			var pixelSpan = new Span<byte>((void*) pixelData, (int) sizeInBytes); | ||||
| 
 | ||||
| 			SetTextureData(textureRegion, pixelSpan, WriteOptions.Unsafe); | ||||
| 
 | ||||
| 			ImageUtils.FreePixelData(pixelData); | ||||
| 		} | ||||
| 
 | ||||
| 		public void SetTextureDataFromCompressed(TextureRegion textureRegion, Stream compressedImageStream) | ||||
| 		{ | ||||
| 			var length = compressedImageStream.Length; | ||||
| 			var buffer = NativeMemory.Alloc((nuint) length); | ||||
| 			var span = new Span<byte>(buffer, (int) length); | ||||
| 			compressedImageStream.ReadExactly(span); | ||||
| 			SetTextureDataFromCompressed(textureRegion, span); | ||||
| 			NativeMemory.Free(buffer); | ||||
| 		} | ||||
| 
 | ||||
| 		public void SetTextureDataFromCompressed(TextureRegion textureRegion, string compressedImageFilePath) | ||||
| 		{ | ||||
| 			var fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read); | ||||
| 			SetTextureDataFromCompressed(textureRegion, fileStream); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Prepares upload of pixel data into a TextureSlice. | ||||
| 		/// </summary> | ||||
| 		public void SetTextureData<T>(TextureRegion textureRegion, Span<T> data, WriteOptions option) where T : unmanaged | ||||
| 		{ | ||||
| 			var elementSize = Marshal.SizeOf<T>(); | ||||
| 			var dataLengthInBytes = (uint) (elementSize * data.Length); | ||||
| 
 | ||||
| 			uint resourceOffset; | ||||
| 			fixed (T* dataPtr = data) | ||||
| 			{ | ||||
| 				resourceOffset = CopyTextureData(dataPtr, dataLengthInBytes, Texture.TexelSize(textureRegion.TextureSlice.Texture.Format)); | ||||
| 			} | ||||
| 
 | ||||
| 			TextureUploads.Add((textureRegion, resourceOffset, option)); | ||||
| 		} | ||||
| 
 | ||||
| 		// Upload | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Uploads all the data corresponding to the created resources. | ||||
| 		/// </summary> | ||||
| 		public void Upload() | ||||
| 		{ | ||||
| 			CopyToTransferBuffer(); | ||||
| 
 | ||||
| 			var commandBuffer = Device.AcquireCommandBuffer(); | ||||
| 			RecordUploadCommands(commandBuffer); | ||||
| 			Device.Submit(commandBuffer); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Uploads and then blocks until the upload is finished. | ||||
| 		/// This is useful for keeping memory usage down during threaded upload. | ||||
| 		/// </summary> | ||||
| 		public void UploadAndWait() | ||||
| 		{ | ||||
| 			CopyToTransferBuffer(); | ||||
| 
 | ||||
| 			var commandBuffer = Device.AcquireCommandBuffer(); | ||||
| 			RecordUploadCommands(commandBuffer); | ||||
| 			var fence = Device.SubmitAndAcquireFence(commandBuffer); | ||||
| 			Device.WaitForFences(fence); | ||||
| 			Device.ReleaseFence(fence); | ||||
| 		} | ||||
| 
 | ||||
| 		// Helper methods | ||||
| 
 | ||||
| 		private void CopyToTransferBuffer() | ||||
| 		{ | ||||
| 			if (BufferUploads.Count > 0) | ||||
| 			{ | ||||
| 				if (BufferTransferBuffer == null || BufferTransferBuffer.Size < bufferDataSize) | ||||
| 				{ | ||||
| 					BufferTransferBuffer?.Dispose(); | ||||
| 					BufferTransferBuffer = new TransferBuffer(Device, TransferUsage.Buffer, bufferDataSize); | ||||
| 				} | ||||
| 
 | ||||
| 				var dataSpan = new Span<byte>(bufferData, (int) bufferDataSize); | ||||
| 				BufferTransferBuffer.SetData(dataSpan, TransferOptions.Cycle); | ||||
| 			} | ||||
| 
 | ||||
| 
 | ||||
| 			if (TextureUploads.Count > 0) | ||||
| 			{ | ||||
| 				if (TextureTransferBuffer == null || TextureTransferBuffer.Size < textureDataSize) | ||||
| 				{ | ||||
| 					TextureTransferBuffer?.Dispose(); | ||||
| 					TextureTransferBuffer = new TransferBuffer(Device, TransferUsage.Texture, textureDataSize); | ||||
| 				} | ||||
| 
 | ||||
| 				var dataSpan = new Span<byte>(textureData, (int) textureDataSize); | ||||
| 				TextureTransferBuffer.SetData(dataSpan, TransferOptions.Cycle); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private void RecordUploadCommands(CommandBuffer commandBuffer) | ||||
| 		{ | ||||
| 			commandBuffer.BeginCopyPass(); | ||||
| 
 | ||||
| 			foreach (var (gpuBuffer, bufferCopyParams, option) in BufferUploads) | ||||
| 			{ | ||||
| 				commandBuffer.UploadToBuffer( | ||||
| 					BufferTransferBuffer, | ||||
| 					gpuBuffer, | ||||
| 					bufferCopyParams, | ||||
| 					option | ||||
| 				); | ||||
| 			} | ||||
| 
 | ||||
| 			foreach (var (textureRegion, offset, option) in TextureUploads) | ||||
| 			{ | ||||
| 				commandBuffer.UploadToTexture( | ||||
| 					TextureTransferBuffer, | ||||
| 					textureRegion, | ||||
| 					new BufferImageCopy( | ||||
| 						offset, | ||||
| 						0, | ||||
| 						0 | ||||
| 					), | ||||
| 					option | ||||
| 				); | ||||
| 			} | ||||
| 
 | ||||
| 			commandBuffer.EndCopyPass(); | ||||
| 
 | ||||
| 			BufferUploads.Clear(); | ||||
| 			TextureUploads.Clear(); | ||||
| 			bufferDataOffset = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		private uint CopyBufferData(void* ptr, uint lengthInBytes) | ||||
| 		{ | ||||
| 			if (bufferDataOffset + lengthInBytes >= bufferDataSize) | ||||
| 			{ | ||||
| 				bufferDataSize = bufferDataOffset + lengthInBytes; | ||||
| 				bufferData = (byte*) NativeMemory.Realloc(bufferData, bufferDataSize); | ||||
| 			} | ||||
| 
 | ||||
| 			var resourceOffset = bufferDataOffset; | ||||
| 
 | ||||
| 			NativeMemory.Copy(ptr, bufferData + bufferDataOffset, lengthInBytes); | ||||
| 			bufferDataOffset += lengthInBytes; | ||||
| 
 | ||||
| 			return resourceOffset; | ||||
| 		} | ||||
| 
 | ||||
| 		private uint CopyTextureData(void* ptr, uint lengthInBytes, uint alignment) | ||||
| 		{ | ||||
| 			textureDataOffset = RoundToAlignment(textureDataOffset, alignment); | ||||
| 
 | ||||
| 			if (textureDataOffset + lengthInBytes >= textureDataSize) | ||||
| 			{ | ||||
| 				textureDataSize = textureDataOffset + lengthInBytes; | ||||
| 				textureData = (byte*) NativeMemory.Realloc(textureData, textureDataSize); | ||||
| 			} | ||||
| 
 | ||||
| 			var resourceOffset = textureDataOffset; | ||||
| 
 | ||||
| 			NativeMemory.Copy(ptr, textureData + textureDataOffset, lengthInBytes); | ||||
| 			textureDataOffset += lengthInBytes; | ||||
| 
 | ||||
| 			return resourceOffset; | ||||
| 		} | ||||
| 
 | ||||
| 		private uint RoundToAlignment(uint value, uint alignment) | ||||
| 		{ | ||||
| 			return alignment * ((value + alignment - 1) / alignment); | ||||
| 		} | ||||
| 
 | ||||
| 		// Dispose | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// It is valid to immediately call Dispose after calling Upload. | ||||
| 		/// </summary> | ||||
| 		protected override void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!IsDisposed) | ||||
| 			{ | ||||
| 				if (disposing) | ||||
| 				{ | ||||
| 					BufferTransferBuffer?.Dispose(); | ||||
| 					TextureTransferBuffer?.Dispose(); | ||||
| 				} | ||||
| 
 | ||||
| 				NativeMemory.Free(bufferData); | ||||
| 			} | ||||
| 			base.Dispose(disposing); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,141 +0,0 @@ | |||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// Buffers are generic data containers that can be used by the GPU. | ||||
| 	/// </summary> | ||||
| 	public class Buffer : RefreshResource | ||||
| 	{ | ||||
| 		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Size in bytes. | ||||
| 		/// </summary> | ||||
| 		public uint Size { get; } | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a buffer of appropriate size given a type and element count. | ||||
| 		/// </summary> | ||||
| 		/// <typeparam name="T">The type that the buffer will contain.</typeparam> | ||||
| 		/// <param name="device">The GraphicsDevice.</param> | ||||
| 		/// <param name="usageFlags">Specifies how the buffer will be used.</param> | ||||
| 		/// <param name="elementCount">How many elements of type T the buffer will contain.</param> | ||||
| 		/// <returns></returns> | ||||
| 		public unsafe static Buffer Create<T>( | ||||
| 			GraphicsDevice device, | ||||
| 			BufferUsageFlags usageFlags, | ||||
| 			uint elementCount | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			return new Buffer( | ||||
| 				device, | ||||
| 				usageFlags, | ||||
| 				(uint) Marshal.SizeOf<T>() * elementCount | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a buffer. | ||||
| 		/// </summary> | ||||
| 		/// <param name="device">An initialized GraphicsDevice.</param> | ||||
| 		/// <param name="usageFlags">Specifies how the buffer will be used.</param> | ||||
| 		/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param> | ||||
| 		public Buffer( | ||||
| 			GraphicsDevice device, | ||||
| 			BufferUsageFlags usageFlags, | ||||
| 			uint sizeInBytes | ||||
| 		) : base(device) | ||||
| 		{ | ||||
| 			Handle = Refresh.Refresh_CreateBuffer( | ||||
| 				device.Handle, | ||||
| 				(Refresh.BufferUsageFlags) usageFlags, | ||||
| 				sizeInBytes | ||||
| 			); | ||||
| 			Size = sizeInBytes; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Reads data out of a buffer and into a span. | ||||
| 		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first. | ||||
| 		/// </summary> | ||||
| 		/// <param name="data">The span that data will be copied to.</param> | ||||
| 		/// <param name="dataLengthInBytes">The length of the data to read.</param> | ||||
| 		public unsafe void GetData<T>( | ||||
| 			Span<T> data, | ||||
| 			uint dataLengthInBytes | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			if (dataLengthInBytes > Size) | ||||
| 			{ | ||||
| 				Logger.LogWarn("Requested too many bytes from buffer!"); | ||||
| 			} | ||||
| 
 | ||||
| 			if (dataLengthInBytes > data.Length * Marshal.SizeOf<T>()) | ||||
| 			{ | ||||
| 				Logger.LogWarn("Data length is larger than the provided Span!"); | ||||
| 			} | ||||
| #endif | ||||
| 
 | ||||
| 			fixed (T* ptr = data) | ||||
| 			{ | ||||
| 				Refresh.Refresh_GetBufferData( | ||||
| 					Device.Handle, | ||||
| 					Handle, | ||||
| 					(IntPtr) ptr, | ||||
| 					dataLengthInBytes | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Reads data out of a buffer and into an array. | ||||
| 		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first. | ||||
| 		/// </summary> | ||||
| 		/// <param name="data">The span that data will be copied to.</param> | ||||
| 		/// <param name="dataLengthInBytes">The length of the data to read.</param> | ||||
| 		public unsafe void GetData<T>( | ||||
| 			T[] data, | ||||
| 			uint dataLengthInBytes | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			GetData(new Span<T>(data), dataLengthInBytes); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Reads data out of a buffer and into a span. | ||||
| 		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first. | ||||
| 		/// Fills the span with as much data from the buffer as it can. | ||||
| 		/// </summary> | ||||
| 		/// <param name="data">The span that data will be copied to.</param> | ||||
| 		public unsafe void GetData<T>( | ||||
| 			Span<T> data | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf<T>(), Size); | ||||
| 			GetData(data, (uint) lengthInBytes); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Reads data out of a buffer and into an array. | ||||
| 		/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first. | ||||
| 		/// Fills the array with as much data from the buffer as it can. | ||||
| 		/// </summary> | ||||
| 		/// <param name="data">The span that data will be copied to.</param> | ||||
| 		public unsafe void GetData<T>( | ||||
| 			T[] data | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf<T>(), Size); | ||||
| 			GetData(new Span<T>(data), (uint) lengthInBytes); | ||||
| 		} | ||||
| 
 | ||||
| 		public static implicit operator BufferBinding(Buffer b) | ||||
| 		{ | ||||
| 			return new BufferBinding(b, 0); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,86 @@ | |||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// GpuBuffers are generic data containers that can be used by the GPU. | ||||
| 	/// </summary> | ||||
| 	public class GpuBuffer : RefreshResource | ||||
| 	{ | ||||
| 		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGpuBuffer; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Size in bytes. | ||||
| 		/// </summary> | ||||
| 		public uint Size { get; } | ||||
| 
 | ||||
| 		private string name; | ||||
| 		public string Name | ||||
| 		{ | ||||
| 			get => name; | ||||
| 
 | ||||
| 			set | ||||
| 			{ | ||||
| 				if (Device.DebugMode) | ||||
| 				{ | ||||
| 					Refresh.Refresh_SetGpuBufferName( | ||||
| 						Device.Handle, | ||||
| 						Handle, | ||||
| 						value | ||||
| 					); | ||||
| 				} | ||||
| 
 | ||||
| 				name = value; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a buffer of appropriate size given a type and element count. | ||||
| 		/// </summary> | ||||
| 		/// <typeparam name="T">The type that the buffer will contain.</typeparam> | ||||
| 		/// <param name="device">The GraphicsDevice.</param> | ||||
| 		/// <param name="usageFlags">Specifies how the buffer will be used.</param> | ||||
| 		/// <param name="elementCount">How many elements of type T the buffer will contain.</param> | ||||
| 		/// <returns></returns> | ||||
| 		public unsafe static GpuBuffer Create<T>( | ||||
| 			GraphicsDevice device, | ||||
| 			BufferUsageFlags usageFlags, | ||||
| 			uint elementCount | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			return new GpuBuffer( | ||||
| 				device, | ||||
| 				usageFlags, | ||||
| 				(uint) Marshal.SizeOf<T>() * elementCount | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a buffer. | ||||
| 		/// </summary> | ||||
| 		/// <param name="device">An initialized GraphicsDevice.</param> | ||||
| 		/// <param name="usageFlags">Specifies how the buffer will be used.</param> | ||||
| 		/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param> | ||||
| 		public GpuBuffer( | ||||
| 			GraphicsDevice device, | ||||
| 			BufferUsageFlags usageFlags, | ||||
| 			uint sizeInBytes | ||||
| 		) : base(device) | ||||
| 		{ | ||||
| 			Handle = Refresh.Refresh_CreateGpuBuffer( | ||||
| 				device.Handle, | ||||
| 				(Refresh.BufferUsageFlags) usageFlags, | ||||
| 				sizeInBytes | ||||
| 			); | ||||
| 			Size = sizeInBytes; | ||||
| 			name = ""; | ||||
| 		} | ||||
| 
 | ||||
| 		public static implicit operator BufferBinding(GpuBuffer b) | ||||
| 		{ | ||||
| 			return new BufferBinding(b, 0); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -62,11 +62,14 @@ namespace MoonWorks.Graphics | |||
| 			refreshGraphicsPipelineCreateInfo.blendConstants[3] = blendConstants.A; | ||||
| 
 | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh(); | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.frontStencilState = depthStencilState.FrontStencilState.ToRefresh(); | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.compareMask = depthStencilState.CompareMask; | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.writeMask = depthStencilState.WriteMask; | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.reference = depthStencilState.Reference; | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp; | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.depthBoundsTestEnable = Conversions.BoolToByte(depthStencilState.DepthBoundsTestEnable); | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.depthTestEnable = Conversions.BoolToByte(depthStencilState.DepthTestEnable); | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.depthWriteEnable = Conversions.BoolToByte(depthStencilState.DepthWriteEnable); | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.frontStencilState = depthStencilState.FrontStencilState.ToRefresh(); | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.maxDepthBounds = depthStencilState.MaxDepthBounds; | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.minDepthBounds = depthStencilState.MinDepthBounds; | ||||
| 			refreshGraphicsPipelineCreateInfo.depthStencilState.stencilTestEnable = Conversions.BoolToByte(depthStencilState.StencilTestEnable); | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| using System; | ||||
| using System.IO; | ||||
| using System.Runtime.InteropServices; | ||||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
|  | @ -15,170 +14,35 @@ namespace MoonWorks.Graphics | |||
| 		public uint Depth { get; } | ||||
| 		public TextureFormat Format { get; internal set; } | ||||
| 		public bool IsCube { get; } | ||||
| 		public uint LayerCount { get; } | ||||
| 		public uint LevelCount { get; } | ||||
| 		public SampleCount SampleCount { get; } | ||||
| 		public TextureUsageFlags UsageFlags { get; } | ||||
| 		public uint Size { get; } | ||||
| 
 | ||||
| 		private string name; | ||||
| 		public string Name | ||||
| 		{ | ||||
| 			get => name; | ||||
| 
 | ||||
| 			set | ||||
| 			{ | ||||
| 				if (Device.DebugMode) | ||||
| 				{ | ||||
| 					Refresh.Refresh_SetTextureName( | ||||
| 						Device.Handle, | ||||
| 						Handle, | ||||
| 						value | ||||
| 					); | ||||
| 				} | ||||
| 
 | ||||
| 				name = value; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: this allocates a delegate instance | ||||
| 		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 2D Texture using PNG or QOI data from raw byte data. | ||||
| 		/// </summary> | ||||
| 		public static unsafe Texture FromImageBytes( | ||||
| 			GraphicsDevice device, | ||||
| 			CommandBuffer commandBuffer, | ||||
| 			Span<byte> data | ||||
| 		) { | ||||
| 			Texture texture; | ||||
| 
 | ||||
| 			fixed (byte *dataPtr = data) | ||||
| 			{ | ||||
| 				var pixels = Refresh.Refresh_Image_Load((nint) dataPtr, data.Length, out var width, out var height, out var len); | ||||
| 
 | ||||
| 				TextureCreateInfo textureCreateInfo = new TextureCreateInfo(); | ||||
| 				textureCreateInfo.Width = (uint) width; | ||||
| 				textureCreateInfo.Height = (uint) height; | ||||
| 				textureCreateInfo.Depth = 1; | ||||
| 				textureCreateInfo.Format = TextureFormat.R8G8B8A8; | ||||
| 				textureCreateInfo.IsCube = false; | ||||
| 				textureCreateInfo.LevelCount = 1; | ||||
| 				textureCreateInfo.SampleCount = SampleCount.One; | ||||
| 				textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler; | ||||
| 
 | ||||
| 				texture = new Texture(device, textureCreateInfo); | ||||
| 				commandBuffer.SetTextureData(texture, pixels, (uint) len); | ||||
| 
 | ||||
| 				Refresh.Refresh_Image_Free(pixels); | ||||
| 			} | ||||
| 
 | ||||
| 			return texture; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 2D Texture using PNG or QOI data from a stream. | ||||
| 		/// </summary> | ||||
| 		public static unsafe Texture FromImageStream( | ||||
| 			GraphicsDevice device, | ||||
| 			CommandBuffer commandBuffer, | ||||
| 			Stream stream | ||||
| 		) { | ||||
| 			var length = stream.Length; | ||||
| 			var buffer = NativeMemory.Alloc((nuint) length); | ||||
| 			var span = new Span<byte>(buffer, (int) length); | ||||
| 			stream.ReadExactly(span); | ||||
| 
 | ||||
| 			var texture = FromImageBytes(device, commandBuffer, span); | ||||
| 
 | ||||
| 			NativeMemory.Free((void*) buffer); | ||||
| 
 | ||||
| 			return texture; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 2D Texture using PNG or QOI data from a file. | ||||
| 		/// </summary> | ||||
| 		public static Texture FromImageFile( | ||||
| 			GraphicsDevice device, | ||||
| 			CommandBuffer commandBuffer, | ||||
| 			string path | ||||
| 		) { | ||||
| 			var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); | ||||
| 			return FromImageStream(device, commandBuffer, fileStream); | ||||
| 		} | ||||
| 
 | ||||
| 		public static unsafe void SetDataFromImageBytes( | ||||
| 			CommandBuffer commandBuffer, | ||||
| 			TextureSlice textureSlice, | ||||
| 			Span<byte> data | ||||
| 		) { | ||||
| 			fixed (byte* ptr = data) | ||||
| 			{ | ||||
| 				var pixels = Refresh.Refresh_Image_Load( | ||||
| 					(nint) ptr, | ||||
| 					(int) data.Length, | ||||
| 					out var w, | ||||
| 					out var h, | ||||
| 					out var len | ||||
| 				); | ||||
| 
 | ||||
| 				commandBuffer.SetTextureData(textureSlice, pixels, (uint) len); | ||||
| 
 | ||||
| 				Refresh.Refresh_Image_Free(pixels); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Sets data for a texture slice using PNG or QOI data from a stream. | ||||
| 		/// </summary> | ||||
| 		public static unsafe void SetDataFromImageStream( | ||||
| 			CommandBuffer commandBuffer, | ||||
| 			TextureSlice textureSlice, | ||||
| 			Stream stream | ||||
| 		) { | ||||
| 			var length = stream.Length; | ||||
| 			var buffer = NativeMemory.Alloc((nuint) length); | ||||
| 			var span = new Span<byte>(buffer, (int) length); | ||||
| 			stream.ReadExactly(span); | ||||
| 
 | ||||
| 			SetDataFromImageBytes(commandBuffer, textureSlice, span); | ||||
| 
 | ||||
| 			NativeMemory.Free((void*) buffer); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Sets data for a texture slice using PNG or QOI data from a file. | ||||
| 		/// </summary> | ||||
| 		public static void SetDataFromImageFile( | ||||
| 			CommandBuffer commandBuffer, | ||||
| 			TextureSlice textureSlice, | ||||
| 			string path | ||||
| 		) { | ||||
| 			var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); | ||||
| 			SetDataFromImageStream(commandBuffer, textureSlice, fileStream); | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream) | ||||
| 		{ | ||||
| 			using var reader = new BinaryReader(stream); | ||||
| 			Texture texture; | ||||
| 			int faces; | ||||
| 			ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube); | ||||
| 
 | ||||
| 			if (isCube) | ||||
| 			{ | ||||
| 				texture = CreateTextureCube(graphicsDevice, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels); | ||||
| 				faces = 6; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels); | ||||
| 				faces = 1; | ||||
| 			} | ||||
| 
 | ||||
| 			for (int i = 0; i < faces; i += 1) | ||||
| 			{ | ||||
| 				for (int j = 0; j < levels; j += 1) | ||||
| 				{ | ||||
| 					var levelWidth = width >> j; | ||||
| 					var levelHeight = height >> j; | ||||
| 
 | ||||
| 					var levelSize = CalculateDDSLevelSize(levelWidth, levelHeight, format); | ||||
| 					var byteBuffer = NativeMemory.Alloc((nuint) levelSize); | ||||
| 					var byteSpan = new Span<byte>(byteBuffer, levelSize); | ||||
| 					stream.ReadExactly(byteSpan); | ||||
| 
 | ||||
| 					var textureSlice = new TextureSlice(texture, new Rect(0, 0, levelWidth, levelHeight), 0, (uint) i, (uint) j); | ||||
| 					commandBuffer.SetTextureData(textureSlice, (nint) byteBuffer, (uint) levelSize); | ||||
| 
 | ||||
| 					NativeMemory.Free(byteBuffer); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			return texture; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 2D texture. | ||||
| 		/// </summary> | ||||
|  | @ -203,6 +67,7 @@ namespace MoonWorks.Graphics | |||
| 				Height = height, | ||||
| 				Depth = 1, | ||||
| 				IsCube = false, | ||||
| 				LayerCount = 1, | ||||
| 				LevelCount = levelCount, | ||||
| 				SampleCount = sampleCount, | ||||
| 				Format = format, | ||||
|  | @ -213,15 +78,43 @@ namespace MoonWorks.Graphics | |||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 3D texture. | ||||
| 		/// Creates a 2D texture array. | ||||
| 		/// </summary> | ||||
| 		/// <param name="device">An initialized GraphicsDevice.</param> | ||||
| 		/// <param name="width">The width of the texture.</param> | ||||
| 		/// <param name="height">The height of the texture.</param> | ||||
| 		/// <param name="depth">The depth of the texture.</param> | ||||
| 		/// <param name="layerCount">The layer count of the texture.</param> | ||||
| 		/// <param name="format">The format of the texture.</param> | ||||
| 		/// <param name="usageFlags">Specifies how the texture will be used.</param> | ||||
| 		/// <param name="levelCount">Specifies the number of mip levels.</param> | ||||
| 		public static Texture CreateTexture2DArray( | ||||
| 			GraphicsDevice device, | ||||
| 			uint width, | ||||
| 			uint height, | ||||
| 			uint layerCount, | ||||
| 			TextureFormat format, | ||||
| 			TextureUsageFlags usageFlags, | ||||
| 			uint levelCount = 1 | ||||
| 		) { | ||||
| 			var textureCreateInfo = new TextureCreateInfo | ||||
| 			{ | ||||
| 				Width = width, | ||||
| 				Height = height, | ||||
| 				Depth = 1, | ||||
| 				IsCube = false, | ||||
| 				LayerCount = layerCount, | ||||
| 				LevelCount = levelCount, | ||||
| 				Format = format, | ||||
| 				UsageFlags = usageFlags | ||||
| 			}; | ||||
| 
 | ||||
| 			return new Texture(device, textureCreateInfo); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a 3D texture. | ||||
| 		/// Note that the width, height and depth all form one slice and cannot be subdivided in a texture slice. | ||||
| 		/// </summary> | ||||
| 		public static Texture CreateTexture3D( | ||||
| 			GraphicsDevice device, | ||||
| 			uint width, | ||||
|  | @ -237,6 +130,7 @@ namespace MoonWorks.Graphics | |||
| 				Height = height, | ||||
| 				Depth = depth, | ||||
| 				IsCube = false, | ||||
| 				LayerCount = 1, | ||||
| 				LevelCount = levelCount, | ||||
| 				Format = format, | ||||
| 				UsageFlags = usageFlags | ||||
|  | @ -266,6 +160,7 @@ namespace MoonWorks.Graphics | |||
| 				Height = size, | ||||
| 				Depth = 1, | ||||
| 				IsCube = true, | ||||
| 				LayerCount = 6, | ||||
| 				LevelCount = levelCount, | ||||
| 				Format = format, | ||||
| 				UsageFlags = usageFlags | ||||
|  | @ -294,16 +189,15 @@ namespace MoonWorks.Graphics | |||
| 			Height = textureCreateInfo.Height; | ||||
| 			Depth = textureCreateInfo.Depth; | ||||
| 			IsCube = textureCreateInfo.IsCube; | ||||
| 			LayerCount = textureCreateInfo.LayerCount; | ||||
| 			LevelCount = textureCreateInfo.LevelCount; | ||||
| 			SampleCount = textureCreateInfo.SampleCount; | ||||
| 			UsageFlags = textureCreateInfo.UsageFlags; | ||||
| 			Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format); | ||||
| 			name = ""; | ||||
| 		} | ||||
| 
 | ||||
| 		public static implicit operator TextureSlice(Texture t) => new TextureSlice(t); | ||||
| 
 | ||||
| 		// Used by AcquireSwapchainTexture. | ||||
| 		// Should not be tracked, because swapchain textures are managed by Vulkan. | ||||
| 		// Used by Window. Swapchain texture handles are managed by the driver backend. | ||||
| 		internal Texture( | ||||
| 			GraphicsDevice device, | ||||
| 			TextureFormat format | ||||
|  | @ -322,333 +216,6 @@ namespace MoonWorks.Graphics | |||
| 			Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format); | ||||
| 		} | ||||
| 
 | ||||
| 		// DDS loading extension, based on MojoDDS | ||||
| 		// Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194 | ||||
| 		private static void ParseDDS( | ||||
| 			BinaryReader reader, | ||||
| 			out TextureFormat format, | ||||
| 			out int width, | ||||
| 			out int height, | ||||
| 			out int levels, | ||||
| 			out bool isCube | ||||
| 		) { | ||||
| 			// A whole bunch of magic numbers, yay DDS! | ||||
| 			const uint DDS_MAGIC = 0x20534444; | ||||
| 			const uint DDS_HEADERSIZE = 124; | ||||
| 			const uint DDS_PIXFMTSIZE = 32; | ||||
| 			const uint DDSD_HEIGHT = 0x2; | ||||
| 			const uint DDSD_WIDTH = 0x4; | ||||
| 			const uint DDSD_PITCH = 0x8; | ||||
| 			const uint DDSD_LINEARSIZE = 0x80000; | ||||
| 			const uint DDSD_REQ = ( | ||||
| 				DDSD_HEIGHT | DDSD_WIDTH | ||||
| 			); | ||||
| 			const uint DDSCAPS_MIPMAP = 0x400000; | ||||
| 			const uint DDSCAPS_TEXTURE = 0x1000; | ||||
| 			const uint DDSCAPS2_CUBEMAP = 0x200; | ||||
| 			const uint DDPF_FOURCC = 0x4; | ||||
| 			const uint DDPF_RGB = 0x40; | ||||
| 			const uint FOURCC_DXT1 = 0x31545844; | ||||
| 			const uint FOURCC_DXT3 = 0x33545844; | ||||
| 			const uint FOURCC_DXT5 = 0x35545844; | ||||
| 			const uint FOURCC_DX10 = 0x30315844; | ||||
| 			const uint pitchAndLinear = ( | ||||
| 				DDSD_PITCH | DDSD_LINEARSIZE | ||||
| 			); | ||||
| 
 | ||||
| 			// File should start with 'DDS ' | ||||
| 			if (reader.ReadUInt32() != DDS_MAGIC) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Not a DDS!"); | ||||
| 			} | ||||
| 
 | ||||
| 			// Texture info | ||||
| 			uint size = reader.ReadUInt32(); | ||||
| 			if (size != DDS_HEADERSIZE) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Invalid DDS header!"); | ||||
| 			} | ||||
| 			uint flags = reader.ReadUInt32(); | ||||
| 			if ((flags & DDSD_REQ) != DDSD_REQ) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Invalid DDS flags!"); | ||||
| 			} | ||||
| 			if ((flags & pitchAndLinear) == pitchAndLinear) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Invalid DDS flags!"); | ||||
| 			} | ||||
| 			height = reader.ReadInt32(); | ||||
| 			width = reader.ReadInt32(); | ||||
| 			reader.ReadUInt32(); // dwPitchOrLinearSize, unused | ||||
| 			reader.ReadUInt32(); // dwDepth, unused | ||||
| 			levels = reader.ReadInt32(); | ||||
| 
 | ||||
| 			// "Reserved" | ||||
| 			reader.ReadBytes(4 * 11); | ||||
| 
 | ||||
| 			// Format info | ||||
| 			uint formatSize = reader.ReadUInt32(); | ||||
| 			if (formatSize != DDS_PIXFMTSIZE) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Bogus PIXFMTSIZE!"); | ||||
| 			} | ||||
| 			uint formatFlags = reader.ReadUInt32(); | ||||
| 			uint formatFourCC = reader.ReadUInt32(); | ||||
| 			uint formatRGBBitCount = reader.ReadUInt32(); | ||||
| 			uint formatRBitMask = reader.ReadUInt32(); | ||||
| 			uint formatGBitMask = reader.ReadUInt32(); | ||||
| 			uint formatBBitMask = reader.ReadUInt32(); | ||||
| 			uint formatABitMask = reader.ReadUInt32(); | ||||
| 
 | ||||
| 			// dwCaps "stuff" | ||||
| 			uint caps = reader.ReadUInt32(); | ||||
| 			if ((caps & DDSCAPS_TEXTURE) == 0) | ||||
| 			{ | ||||
| 				throw new NotSupportedException("Not a texture!"); | ||||
| 			} | ||||
| 
 | ||||
| 			isCube = false; | ||||
| 
 | ||||
| 			uint caps2 = reader.ReadUInt32(); | ||||
| 			if (caps2 != 0) | ||||
| 			{ | ||||
| 				if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP) | ||||
| 				{ | ||||
| 					isCube = true; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					throw new NotSupportedException("Invalid caps2!"); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			reader.ReadUInt32(); // dwCaps3, unused | ||||
| 			reader.ReadUInt32(); // dwCaps4, unused | ||||
| 
 | ||||
| 			// "Reserved" | ||||
| 			reader.ReadUInt32(); | ||||
| 
 | ||||
| 			// Mipmap sanity check | ||||
| 			if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP) | ||||
| 			{ | ||||
| 				levels = 1; | ||||
| 			} | ||||
| 
 | ||||
| 			// Determine texture format | ||||
| 			if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC) | ||||
| 			{ | ||||
| 				switch (formatFourCC) | ||||
| 				{ | ||||
| 					case 0x71: // D3DFMT_A16B16G16R16F | ||||
| 						format = TextureFormat.R16G16B16A16_SFLOAT; | ||||
| 						break; | ||||
| 					case 0x74: // D3DFMT_A32B32G32R32F | ||||
| 						format = TextureFormat.R32G32B32A32_SFLOAT; | ||||
| 						break; | ||||
| 					case FOURCC_DXT1: | ||||
| 						format = TextureFormat.BC1; | ||||
| 						break; | ||||
| 					case FOURCC_DXT3: | ||||
| 						format = TextureFormat.BC2; | ||||
| 						break; | ||||
| 					case FOURCC_DXT5: | ||||
| 						format = TextureFormat.BC3; | ||||
| 						break; | ||||
| 					case FOURCC_DX10: | ||||
| 						// If the fourCC is DX10, there is an extra header with additional format information. | ||||
| 						uint dxgiFormat = reader.ReadUInt32(); | ||||
| 
 | ||||
| 						// These values are taken from the DXGI_FORMAT enum. | ||||
| 						switch (dxgiFormat) | ||||
| 						{ | ||||
| 							case 2: | ||||
| 								format = TextureFormat.R32G32B32A32_SFLOAT; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 10: | ||||
| 								format = TextureFormat.R16G16B16A16_SFLOAT; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 71: | ||||
| 								format = TextureFormat.BC1; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 74: | ||||
| 								format = TextureFormat.BC2; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 77: | ||||
| 								format = TextureFormat.BC3; | ||||
| 								break; | ||||
| 
 | ||||
| 							case 98: | ||||
| 								format = TextureFormat.BC7; | ||||
| 								break; | ||||
| 
 | ||||
| 							default: | ||||
| 								throw new NotSupportedException( | ||||
| 									"Unsupported DDS texture format" | ||||
| 								); | ||||
| 						} | ||||
| 
 | ||||
| 						uint resourceDimension = reader.ReadUInt32(); | ||||
| 
 | ||||
| 						// These values are taken from the D3D10_RESOURCE_DIMENSION enum. | ||||
| 						switch (resourceDimension) | ||||
| 						{ | ||||
| 							case 0: // Unknown | ||||
| 							case 1: // Buffer | ||||
| 								throw new NotSupportedException( | ||||
| 									"Unsupported DDS texture format" | ||||
| 								); | ||||
| 							default: | ||||
| 								break; | ||||
| 						} | ||||
| 
 | ||||
| 						/* | ||||
| 						 * This flag seemingly only indicates if the texture is a cube map. | ||||
| 						 * This is already determined above. Cool! | ||||
| 						 */ | ||||
| 						uint miscFlag = reader.ReadUInt32(); | ||||
| 
 | ||||
| 						/* | ||||
| 						 * Indicates the number of elements in the texture array. | ||||
| 						 * We don't support texture arrays so just throw if it's greater than 1. | ||||
| 						 */ | ||||
| 						uint arraySize = reader.ReadUInt32(); | ||||
| 
 | ||||
| 						if (arraySize > 1) | ||||
| 						{ | ||||
| 							throw new NotSupportedException( | ||||
| 								"Unsupported DDS texture format" | ||||
| 							); | ||||
| 						} | ||||
| 
 | ||||
| 						reader.ReadUInt32(); // reserved | ||||
| 
 | ||||
| 						break; | ||||
| 					default: | ||||
| 						throw new NotSupportedException( | ||||
| 							"Unsupported DDS texture format" | ||||
| 						); | ||||
| 				} | ||||
| 			} | ||||
| 			else if ((formatFlags & DDPF_RGB) == DDPF_RGB) | ||||
| 			{ | ||||
| 				if (	formatRGBBitCount != 32 || | ||||
| 					formatRBitMask != 0x00FF0000 || | ||||
| 					formatGBitMask != 0x0000FF00 || | ||||
| 					formatBBitMask != 0x000000FF || | ||||
| 					formatABitMask != 0xFF000000	) | ||||
| 				{ | ||||
| 					throw new NotSupportedException( | ||||
| 						"Unsupported DDS texture format" | ||||
| 					); | ||||
| 				} | ||||
| 
 | ||||
| 				format = TextureFormat.B8G8R8A8; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				throw new NotSupportedException( | ||||
| 					"Unsupported DDS texture format" | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private static int CalculateDDSLevelSize( | ||||
| 			int width, | ||||
| 			int height, | ||||
| 			TextureFormat format | ||||
| 		) { | ||||
| 			if (format == TextureFormat.R8G8B8A8) | ||||
| 			{ | ||||
| 				return (((width * 32) + 7) / 8) * height; | ||||
| 			} | ||||
| 			else if (format == TextureFormat.R16G16B16A16_SFLOAT) | ||||
| 			{ | ||||
| 				return (((width * 64) + 7) / 8) * height; | ||||
| 			} | ||||
| 			else if (format == TextureFormat.R32G32B32A32_SFLOAT) | ||||
| 			{ | ||||
| 				return (((width * 128) + 7) / 8) * height; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				int blockSize = 16; | ||||
| 				if (format == TextureFormat.BC1) | ||||
| 				{ | ||||
| 					blockSize = 8; | ||||
| 				} | ||||
| 				width = System.Math.Max(width, 1); | ||||
| 				height = System.Math.Max(height, 1); | ||||
| 				return ( | ||||
| 					((width + 3) / 4) * | ||||
| 					((height + 3) / 4) * | ||||
| 					blockSize | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Asynchronously saves RGBA or BGRA pixel data to a file in PNG format. <br/> | ||||
| 		/// Warning: this is expensive and will block to wait for data download from GPU! <br/> | ||||
| 		/// You can avoid blocking by calling this method from a thread. | ||||
| 		/// </summary> | ||||
| 		public unsafe void SavePNG(string path) | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			if (Format != TextureFormat.R8G8B8A8 && Format != TextureFormat.B8G8R8A8) | ||||
| 			{ | ||||
| 				throw new ArgumentException("Texture format must be RGBA or BGRA!", "format"); | ||||
| 			} | ||||
| #endif | ||||
| 
 | ||||
| 			var buffer = new Buffer(Device, 0, Width * Height * 4); // this creates garbage... oh well | ||||
| 
 | ||||
| 			// immediately request the data copy | ||||
| 			var commandBuffer = Device.AcquireCommandBuffer(); | ||||
| 			commandBuffer.CopyTextureToBuffer(this, buffer); | ||||
| 			var fence = Device.SubmitAndAcquireFence(commandBuffer); | ||||
| 
 | ||||
| 			var byteCount = buffer.Size; | ||||
| 
 | ||||
| 			var pixelsPtr = NativeMemory.Alloc((nuint) byteCount); | ||||
| 			var pixelsSpan = new Span<byte>(pixelsPtr, (int) byteCount); | ||||
| 
 | ||||
| 			Device.WaitForFences(fence); // make sure the data transfer is done... | ||||
| 			Device.ReleaseFence(fence); // and then release the fence | ||||
| 
 | ||||
| 			buffer.GetData(pixelsSpan); | ||||
| 
 | ||||
| 			if (Format == TextureFormat.B8G8R8A8) | ||||
| 			{ | ||||
| 				var rgbaPtr = NativeMemory.Alloc((nuint) byteCount); | ||||
| 				var rgbaSpan = new Span<byte>(rgbaPtr, (int) byteCount); | ||||
| 
 | ||||
| 				for (var i = 0; i < byteCount; i += 4) | ||||
| 				{ | ||||
| 					rgbaSpan[i] = pixelsSpan[i + 2]; | ||||
| 					rgbaSpan[i + 1] = pixelsSpan[i + 1]; | ||||
| 					rgbaSpan[i + 2] = pixelsSpan[i]; | ||||
| 					rgbaSpan[i + 3] = pixelsSpan[i + 3]; | ||||
| 				} | ||||
| 
 | ||||
| 				Refresh.Refresh_Image_SavePNG(path, (nint) rgbaPtr, (int) Width, (int) Height); | ||||
| 
 | ||||
| 				NativeMemory.Free((void*) rgbaPtr); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				fixed (byte* ptr = pixelsSpan) | ||||
| 				{ | ||||
| 					Refresh.Refresh_Image_SavePNG(path, (nint) ptr, (int) Width, (int) Height); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			NativeMemory.Free(pixelsPtr); | ||||
| 		} | ||||
| 
 | ||||
| 		public static uint BytesPerPixel(TextureFormat format) | ||||
| 		{ | ||||
| 			switch (format) | ||||
|  | @ -697,6 +264,21 @@ namespace MoonWorks.Graphics | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public static uint TexelSize(TextureFormat format) | ||||
| 		{ | ||||
| 			switch (format) | ||||
| 			{ | ||||
| 				case TextureFormat.BC2: | ||||
| 				case TextureFormat.BC3: | ||||
| 				case TextureFormat.BC7: | ||||
| 					return 16; | ||||
| 				case TextureFormat.BC1: | ||||
| 					return 8; | ||||
| 				default: | ||||
| 					return 1; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public static uint BlockSizeSquared(TextureFormat format) | ||||
| 		{ | ||||
| 			switch (format) | ||||
|  | @ -739,5 +321,8 @@ namespace MoonWorks.Graphics | |||
| 					return 0; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public static implicit operator TextureSlice(Texture t) => new TextureSlice(t); | ||||
| 		public static implicit operator TextureRegion(Texture t) => new TextureRegion(t); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,157 @@ | |||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	public unsafe class TransferBuffer : RefreshResource | ||||
| 	{ | ||||
| 		protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTransferBuffer; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Size in bytes. | ||||
| 		/// </summary> | ||||
| 		public uint Size { get; } | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a buffer of requested size given a type and element count. | ||||
| 		/// </summary> | ||||
| 		/// <typeparam name="T">The type that the buffer will contain.</typeparam> | ||||
| 		/// <param name="device">The GraphicsDevice.</param> | ||||
| 		/// <param name="elementCount">How many elements of type T the buffer will contain.</param> | ||||
| 		/// <returns></returns> | ||||
| 		public unsafe static TransferBuffer Create<T>( | ||||
| 			GraphicsDevice device, | ||||
| 			TransferUsage usage, | ||||
| 			uint elementCount | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			return new TransferBuffer( | ||||
| 				device, | ||||
| 				usage, | ||||
| 				(uint) Marshal.SizeOf<T>() * elementCount | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Creates a TransferBuffer. | ||||
| 		/// </summary> | ||||
| 		/// <param name="device">An initialized GraphicsDevice.</param> | ||||
| 		/// <param name="sizeInBytes">The length of the buffer. Cannot be resized.</param> | ||||
| 		/// <param name="usage">Whether this will be used to upload buffers or textures.</param> | ||||
| 		public TransferBuffer( | ||||
| 			GraphicsDevice device, | ||||
| 			TransferUsage usage, | ||||
| 			uint sizeInBytes | ||||
| 		) : base(device) | ||||
| 		{ | ||||
| 			Handle = Refresh.Refresh_CreateTransferBuffer( | ||||
| 				device.Handle, | ||||
| 				(Refresh.TransferUsage) usage, | ||||
| 				sizeInBytes | ||||
| 			); | ||||
| 			Size = sizeInBytes; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Immediately copies data from a Span to the TransferBuffer. | ||||
| 		/// Returns the length of the copy in bytes. | ||||
| 		/// | ||||
| 		/// If setDataOption is DISCARD and this TransferBuffer was used in an Upload command, | ||||
| 		/// that command will still use the correct data at the cost of increased memory usage. | ||||
| 		/// | ||||
| 		/// If setDataOption is OVERWRITE and this TransferBuffer was used in an Upload command, | ||||
| 		/// the data will be overwritten immediately, which could cause a data race. | ||||
| 		/// </summary> | ||||
| 		public unsafe uint SetData<T>( | ||||
| 			Span<T> data, | ||||
| 			uint bufferOffsetInBytes, | ||||
| 			TransferOptions setDataOption | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			var elementSize = Marshal.SizeOf<T>(); | ||||
| 			var dataLengthInBytes = (uint) (elementSize * data.Length); | ||||
| 
 | ||||
| #if DEBUG | ||||
| 			AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes); | ||||
| #endif | ||||
| 
 | ||||
| 			fixed (T* dataPtr = data) | ||||
| 			{ | ||||
| 				Refresh.Refresh_SetTransferData( | ||||
| 					Device.Handle, | ||||
| 					(nint) dataPtr, | ||||
| 					Handle, | ||||
| 					new Refresh.BufferCopy | ||||
| 					{ | ||||
| 						srcOffset = 0, | ||||
| 						dstOffset = bufferOffsetInBytes, | ||||
| 						size = dataLengthInBytes | ||||
| 					}, | ||||
| 					(Refresh.TransferOptions) setDataOption | ||||
| 				); | ||||
| 			} | ||||
| 
 | ||||
| 			return dataLengthInBytes; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Immediately copies data from a Span to the TransferBuffer. | ||||
| 		/// Returns the length of the copy in bytes. | ||||
| 		/// | ||||
| 		/// If setDataOption is DISCARD and this TransferBuffer was used in an Upload command, | ||||
| 		/// that command will still use the correct data at the cost of increased memory usage. | ||||
| 		/// | ||||
| 		/// If setDataOption is OVERWRITE and this TransferBuffer was used in an Upload command, | ||||
| 		/// the data will be overwritten immediately, which could cause a data race. | ||||
| 		/// </summary> | ||||
| 		public unsafe uint SetData<T>( | ||||
| 			Span<T> data, | ||||
| 			TransferOptions setDataOption | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			return SetData(data, 0, setDataOption); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Immediately copies data from the TransferBuffer into a Span. | ||||
| 		/// </summary> | ||||
| 		public unsafe void GetData<T>( | ||||
| 			Span<T> data, | ||||
| 			uint bufferOffsetInBytes = 0 | ||||
| 		) where T : unmanaged | ||||
| 		{ | ||||
| 			var elementSize = Marshal.SizeOf<T>(); | ||||
| 			var dataLengthInBytes = (uint) (elementSize * data.Length); | ||||
| 
 | ||||
| #if DEBUG | ||||
| 			AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes); | ||||
| #endif | ||||
| 
 | ||||
| 			fixed (T* dataPtr = data) | ||||
| 			{ | ||||
| 				Refresh.Refresh_GetTransferData( | ||||
| 					Device.Handle, | ||||
| 					Handle, | ||||
| 					(nint) dataPtr, | ||||
| 					new Refresh.BufferCopy | ||||
| 					{ | ||||
| 						srcOffset = bufferOffsetInBytes, | ||||
| 						dstOffset = 0, | ||||
| 						size = dataLengthInBytes | ||||
| 					} | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| #if DEBUG | ||||
| 		private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes) | ||||
| 		{ | ||||
| 			if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes) | ||||
| 			{ | ||||
| 				throw new InvalidOperationException($"Data overflow! Transfer buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}"); | ||||
| 			} | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
|  | @ -11,15 +11,30 @@ | |||
| 		public bool DepthTestEnable; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Describes the stencil operation for back-facing primitives. | ||||
| 		/// Describes the back-face stencil operation. | ||||
| 		/// </summary> | ||||
| 		public StencilOpState BackStencilState; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Describes the stencil operation for front-facing primitives. | ||||
| 		/// Describes the front-face stencil operation. | ||||
| 		/// </summary> | ||||
| 		public StencilOpState FrontStencilState; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// The compare mask for stencil ops. | ||||
| 		/// </summary> | ||||
| 		public uint CompareMask; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// The write mask for stencil ops. | ||||
| 		/// </summary> | ||||
| 		public uint WriteMask; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// The stencil reference value. | ||||
| 		/// </summary> | ||||
| 		public uint Reference; | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// The comparison operator used in the depth test. | ||||
| 		/// </summary> | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ namespace MoonWorks.Graphics | |||
| 		public uint Height; | ||||
| 		public uint Depth; | ||||
| 		public bool IsCube; | ||||
| 		public uint LayerCount; | ||||
| 		public uint LevelCount; | ||||
| 		public SampleCount SampleCount; | ||||
| 		public TextureFormat Format; | ||||
|  | @ -24,6 +25,7 @@ namespace MoonWorks.Graphics | |||
| 				height = Height, | ||||
| 				depth = Depth, | ||||
| 				isCube = Conversions.BoolToByte(IsCube), | ||||
| 				layerCount = LayerCount, | ||||
| 				levelCount = LevelCount, | ||||
| 				sampleCount = (Refresh.SampleCount) SampleCount, | ||||
| 				format = (Refresh.TextureFormat) Format, | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -0,0 +1,12 @@ | |||
| #version 450 | ||||
| 
 | ||||
| layout (location = 0) in vec2 TexCoord; | ||||
| 
 | ||||
| layout (location = 0) out vec4 FragColor; | ||||
| 
 | ||||
| layout (binding = 0, set = 1) uniform sampler2D TexSampler; | ||||
| 
 | ||||
| void main() | ||||
| { | ||||
| 	FragColor = texture(TexSampler, TexCoord); | ||||
| } | ||||
|  | @ -0,0 +1,46 @@ | |||
| using RefreshCS; | ||||
| 
 | ||||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// A texture region specifies a subregion of a texture. | ||||
| 	/// These are used by copy commands. | ||||
| 	/// </summary> | ||||
| 	public struct TextureRegion | ||||
| 	{ | ||||
| 		public TextureSlice TextureSlice; | ||||
| 		public uint X; | ||||
| 		public uint Y; | ||||
| 		public uint Z; | ||||
| 		public uint Width; | ||||
| 		public uint Height; | ||||
| 		public uint Depth; | ||||
| 
 | ||||
| 		public uint Size => (Width * Height * Depth * Texture.BytesPerPixel(TextureSlice.Texture.Format) / Texture.BlockSizeSquared(TextureSlice.Texture.Format)) >> (int) TextureSlice.MipLevel; | ||||
| 
 | ||||
| 		public TextureRegion(Texture texture) | ||||
| 		{ | ||||
| 			TextureSlice = new TextureSlice(texture); | ||||
| 			X = 0; | ||||
| 			Y = 0; | ||||
| 			Z = 0; | ||||
| 			Width = texture.Width; | ||||
| 			Height = texture.Height; | ||||
| 			Depth = texture.Depth; | ||||
| 		} | ||||
| 
 | ||||
| 		public Refresh.TextureRegion ToRefreshTextureRegion() | ||||
| 		{ | ||||
| 			return new Refresh.TextureRegion | ||||
| 			{ | ||||
| 				textureSlice = TextureSlice.ToRefreshTextureSlice(), | ||||
| 				x = X, | ||||
| 				y = Y, | ||||
| 				z = Z, | ||||
| 				w = Width, | ||||
| 				h = Height, | ||||
| 				d = Depth | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -3,55 +3,31 @@ | |||
| namespace MoonWorks.Graphics | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// A texture slice specifies a subregion of a texture. | ||||
| 	/// Many operations can use texture slices in place of textures for the sake of convenience. | ||||
| 	/// A texture slice specifies a subresource of a texture. | ||||
| 	/// </summary> | ||||
| 	public struct TextureSlice | ||||
| 	{ | ||||
| 		public Texture Texture { get; } | ||||
| 		public Rect Rectangle { get; } | ||||
| 		public uint Depth { get; } | ||||
| 		public uint Layer { get; } | ||||
| 		public uint Level { get; } | ||||
| 		public Texture Texture; | ||||
| 		public uint MipLevel; | ||||
| 		public uint Layer; | ||||
| 
 | ||||
| 		public uint Size => (uint) (Rectangle.W * Rectangle.H * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)); | ||||
| 		public uint Size => (Texture.Width * Texture.Height * Texture.Depth * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)) >> (int) MipLevel; | ||||
| 
 | ||||
| 		public TextureSlice(Texture texture) | ||||
| 		{ | ||||
| 			Texture = texture; | ||||
| 			Rectangle = new Rect | ||||
| 			{ | ||||
| 				X = 0, | ||||
| 				Y = 0, | ||||
| 				W = (int) texture.Width, | ||||
| 				H = (int) texture.Height | ||||
| 			}; | ||||
| 			Depth = 0; | ||||
| 			MipLevel = 0; | ||||
| 			Layer = 0; | ||||
| 			Level = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public TextureSlice(Texture texture, Rect rectangle, uint depth = 0, uint layer = 0, uint level = 0) | ||||
| 		{ | ||||
| 			Texture = texture; | ||||
| 			Rectangle = rectangle; | ||||
| 			Depth = depth; | ||||
| 			Layer = layer; | ||||
| 			Level = level; | ||||
| 		} | ||||
| 
 | ||||
| 		public Refresh.TextureSlice ToRefreshTextureSlice() | ||||
| 		{ | ||||
| 			Refresh.TextureSlice textureSlice = new Refresh.TextureSlice | ||||
| 			return new Refresh.TextureSlice | ||||
| 			{ | ||||
| 				texture = Texture.Handle, | ||||
| 				rectangle = Rectangle.ToRefresh(), | ||||
| 				depth = Depth, | ||||
| 				layer = Layer, | ||||
| 				level = Level | ||||
| 				mipLevel = MipLevel, | ||||
| 				layer = Layer | ||||
| 			}; | ||||
| 
 | ||||
| 			return textureSlice; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ namespace MoonWorks.Video | |||
| 		public IntPtr Handle => handle; | ||||
| 		IntPtr handle; | ||||
| 
 | ||||
| 		public bool Loaded => handle != IntPtr.Zero; | ||||
| 		public bool Ended => Dav1dfile.df_eos(Handle) == 1; | ||||
| 
 | ||||
| 		public IntPtr yDataHandle; | ||||
|  | @ -20,21 +21,41 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		public bool FrameDataUpdated { get; set; } | ||||
| 
 | ||||
| 		private VideoAV1 Parent; | ||||
| 
 | ||||
| 		public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device) | ||||
| 		{ | ||||
| 			if (Dav1dfile.df_fopen(video.Filename, out handle) == 0) | ||||
| 			handle = IntPtr.Zero; | ||||
| 			Parent = video; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Load() | ||||
| 		{ | ||||
| 				throw new Exception("Failed to open video file!"); | ||||
| 			if (!Loaded) | ||||
| 			{ | ||||
| 				if (Dav1dfile.df_fopen(Parent.Filename, out handle) == 0) | ||||
| 				{ | ||||
| 					throw new Exception("Failed to load video file!"); | ||||
| 				} | ||||
| 
 | ||||
| 				Reset(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Unload() | ||||
| 		{ | ||||
| 			if (Loaded) | ||||
| 			{ | ||||
| 				Dav1dfile.df_close(handle); | ||||
| 				handle = IntPtr.Zero; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Reset() | ||||
| 		{ | ||||
| 			lock (this) | ||||
| 			{ | ||||
| 				Dav1dfile.df_reset(Handle); | ||||
| 				Dav1dfile.df_reset(handle); | ||||
| 				ReadNextFrame(); | ||||
| 			} | ||||
| 		} | ||||
|  | @ -46,7 +67,7 @@ namespace MoonWorks.Video | |||
| 				if (!Ended) | ||||
| 				{ | ||||
| 					if (Dav1dfile.df_readvideo( | ||||
| 						Handle, | ||||
| 						handle, | ||||
| 						1, | ||||
| 						out var yDataHandle, | ||||
| 						out var uDataHandle, | ||||
|  | @ -74,7 +95,7 @@ namespace MoonWorks.Video | |||
| 		{ | ||||
| 			if (!IsDisposed) | ||||
| 			{ | ||||
| 				Dav1dfile.df_close(Handle); | ||||
| 				Unload(); | ||||
| 			} | ||||
| 			base.Dispose(disposing); | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Threading.Tasks; | ||||
| using MoonWorks.Graphics; | ||||
| 
 | ||||
| namespace MoonWorks.Video | ||||
|  | @ -18,16 +17,13 @@ namespace MoonWorks.Video | |||
| 		private VideoAV1 Video = null; | ||||
| 		private VideoAV1Stream CurrentStream = null; | ||||
| 
 | ||||
| 		private Task ReadNextFrameTask; | ||||
| 		private Task ResetStreamATask; | ||||
| 		private Task ResetStreamBTask; | ||||
| 
 | ||||
| 		private GraphicsDevice GraphicsDevice; | ||||
| 		private Texture yTexture = null; | ||||
| 		private Texture uTexture = null; | ||||
| 		private Texture vTexture = null; | ||||
| 		private Sampler LinearSampler; | ||||
| 
 | ||||
| 		private TransferBuffer TransferBuffer; | ||||
| 
 | ||||
| 		private int currentFrame; | ||||
| 
 | ||||
| 		private Stopwatch timer; | ||||
|  | @ -36,8 +32,6 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		public VideoPlayer(GraphicsDevice device) : base(device) | ||||
| 		{ | ||||
| 			GraphicsDevice = device; | ||||
| 
 | ||||
| 			LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp); | ||||
| 
 | ||||
| 			timer = new Stopwatch(); | ||||
|  | @ -55,46 +49,46 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 				if (RenderTexture == null) | ||||
| 				{ | ||||
| 					RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height); | ||||
| 					RenderTexture = CreateRenderTexture(Device, video.Width, video.Height); | ||||
| 				} | ||||
| 
 | ||||
| 				if (yTexture == null) | ||||
| 				{ | ||||
| 					yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height); | ||||
| 					yTexture = CreateSubTexture(Device, video.Width, video.Height); | ||||
| 				} | ||||
| 
 | ||||
| 				if (uTexture == null) | ||||
| 				{ | ||||
| 					uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); | ||||
| 					uTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight); | ||||
| 				} | ||||
| 
 | ||||
| 				if (vTexture == null) | ||||
| 				{ | ||||
| 					vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); | ||||
| 					vTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight); | ||||
| 				} | ||||
| 
 | ||||
| 				if (video.Width != RenderTexture.Width || video.Height != RenderTexture.Height) | ||||
| 				{ | ||||
| 					RenderTexture.Dispose(); | ||||
| 					RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height); | ||||
| 					RenderTexture = CreateRenderTexture(Device, video.Width, video.Height); | ||||
| 				} | ||||
| 
 | ||||
| 				if (video.Width != yTexture.Width || video.Height != yTexture.Height) | ||||
| 				{ | ||||
| 					yTexture.Dispose(); | ||||
| 					yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height); | ||||
| 					yTexture = CreateSubTexture(Device, video.Width, video.Height); | ||||
| 				} | ||||
| 
 | ||||
| 				if (video.UVWidth != uTexture.Width || video.UVHeight != uTexture.Height) | ||||
| 				{ | ||||
| 					uTexture.Dispose(); | ||||
| 					uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); | ||||
| 					uTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight); | ||||
| 				} | ||||
| 
 | ||||
| 				if (video.UVWidth != vTexture.Width || video.UVHeight != vTexture.Height) | ||||
| 				{ | ||||
| 					vTexture.Dispose(); | ||||
| 					vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); | ||||
| 					vTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight); | ||||
| 				} | ||||
| 
 | ||||
| 				Video = video; | ||||
|  | @ -155,7 +149,7 @@ namespace MoonWorks.Video | |||
| 			lastTimestamp = 0; | ||||
| 			timeElapsed = 0; | ||||
| 
 | ||||
| 			InitializeDav1dStream(); | ||||
| 			ResetDav1dStream(); | ||||
| 
 | ||||
| 			State = VideoState.Stopped; | ||||
| 		} | ||||
|  | @ -165,9 +159,17 @@ namespace MoonWorks.Video | |||
| 		/// </summary> | ||||
| 		public void Unload() | ||||
| 		{ | ||||
| 			Stop(); | ||||
| 			ResetStreamATask?.Wait(); | ||||
| 			ResetStreamBTask?.Wait(); | ||||
| 			timer.Stop(); | ||||
| 			timer.Reset(); | ||||
| 
 | ||||
| 			lastTimestamp = 0; | ||||
| 			timeElapsed = 0; | ||||
| 
 | ||||
| 			State = VideoState.Stopped; | ||||
| 
 | ||||
| 			Video.StreamA.Unload(); | ||||
| 			Video.StreamB.Unload(); | ||||
| 
 | ||||
| 			Video = null; | ||||
| 		} | ||||
| 
 | ||||
|  | @ -194,8 +196,7 @@ namespace MoonWorks.Video | |||
| 				} | ||||
| 
 | ||||
| 				currentFrame = thisFrame; | ||||
| 				ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame); | ||||
| 				ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 				CurrentStream.ReadNextFrame(); | ||||
| 			} | ||||
| 
 | ||||
| 			if (CurrentStream.Ended) | ||||
|  | @ -203,17 +204,7 @@ namespace MoonWorks.Video | |||
| 				timer.Stop(); | ||||
| 				timer.Reset(); | ||||
| 
 | ||||
| 				var task = Task.Run(CurrentStream.Reset); | ||||
| 				task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 
 | ||||
| 				if (CurrentStream == Video.StreamA) | ||||
| 				{ | ||||
| 					ResetStreamATask = task; | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					ResetStreamBTask = task; | ||||
| 				} | ||||
| 				CurrentStream.Reset(); | ||||
| 
 | ||||
| 				if (Loop) | ||||
| 				{ | ||||
|  | @ -233,37 +224,76 @@ namespace MoonWorks.Video | |||
| 		{ | ||||
| 			lock (CurrentStream) | ||||
| 			{ | ||||
| 				var commandBuffer = GraphicsDevice.AcquireCommandBuffer(); | ||||
| 				var commandBuffer = Device.AcquireCommandBuffer(); | ||||
| 
 | ||||
| 				commandBuffer.SetTextureDataYUV( | ||||
| 				var ySpan = new Span<byte>((void*) CurrentStream.yDataHandle, (int) CurrentStream.yDataLength); | ||||
| 				var uSpan = new Span<byte>((void*) CurrentStream.uDataHandle, (int) CurrentStream.uvDataLength); | ||||
| 				var vSpan = new Span<byte>((void*) CurrentStream.vDataHandle, (int) CurrentStream.uvDataLength); | ||||
| 
 | ||||
| 				if (TransferBuffer == null || TransferBuffer.Size < ySpan.Length + uSpan.Length + vSpan.Length) | ||||
| 				{ | ||||
| 					TransferBuffer?.Dispose(); | ||||
| 					TransferBuffer = new TransferBuffer(Device, TransferUsage.Texture, (uint) (ySpan.Length + uSpan.Length + vSpan.Length)); | ||||
| 				} | ||||
| 				TransferBuffer.SetData(ySpan, 0, TransferOptions.Cycle); | ||||
| 				TransferBuffer.SetData(uSpan, (uint) ySpan.Length, TransferOptions.Unsafe); | ||||
| 				TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), TransferOptions.Unsafe); | ||||
| 
 | ||||
| 				commandBuffer.BeginCopyPass(); | ||||
| 
 | ||||
| 				commandBuffer.UploadToTexture( | ||||
| 					TransferBuffer, | ||||
| 					yTexture, | ||||
| 					uTexture, | ||||
| 					vTexture, | ||||
| 					CurrentStream.yDataHandle, | ||||
| 					CurrentStream.uDataHandle, | ||||
| 					CurrentStream.vDataHandle, | ||||
| 					CurrentStream.yDataLength, | ||||
| 					CurrentStream.uvDataLength, | ||||
| 					CurrentStream.yStride, | ||||
| 					CurrentStream.uvStride | ||||
| 					new BufferImageCopy | ||||
| 					{ | ||||
| 						BufferOffset = 0, | ||||
| 						BufferStride = CurrentStream.yStride, | ||||
| 						BufferImageHeight = yTexture.Height | ||||
| 					}, | ||||
| 					WriteOptions.Cycle | ||||
| 				); | ||||
| 
 | ||||
| 				commandBuffer.UploadToTexture( | ||||
| 					TransferBuffer, | ||||
| 					uTexture, | ||||
| 					new BufferImageCopy{ | ||||
| 						BufferOffset = (uint) ySpan.Length, | ||||
| 						BufferStride = CurrentStream.uvStride, | ||||
| 						BufferImageHeight = uTexture.Height | ||||
| 					}, | ||||
| 					WriteOptions.Cycle | ||||
| 				); | ||||
| 
 | ||||
| 				commandBuffer.UploadToTexture( | ||||
| 					TransferBuffer, | ||||
| 					vTexture, | ||||
| 					new BufferImageCopy | ||||
| 					{ | ||||
| 						BufferOffset = (uint) (ySpan.Length + uSpan.Length), | ||||
| 						BufferStride = CurrentStream.uvStride, | ||||
| 						BufferImageHeight = vTexture.Height | ||||
| 					}, | ||||
| 					WriteOptions.Cycle | ||||
| 				); | ||||
| 
 | ||||
| 				commandBuffer.EndCopyPass(); | ||||
| 
 | ||||
| 				commandBuffer.BeginRenderPass( | ||||
| 					new ColorAttachmentInfo(RenderTexture, Color.Black) | ||||
| 					new ColorAttachmentInfo(RenderTexture, WriteOptions.Cycle, Color.Black) | ||||
| 				); | ||||
| 
 | ||||
| 				commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline); | ||||
| 				commandBuffer.BindGraphicsPipeline(Device.VideoPipeline); | ||||
| 				commandBuffer.BindFragmentSamplers( | ||||
| 					new TextureSamplerBinding(yTexture, LinearSampler), | ||||
| 					new TextureSamplerBinding(uTexture, LinearSampler), | ||||
| 					new TextureSamplerBinding(vTexture, LinearSampler) | ||||
| 				); | ||||
| 
 | ||||
| 				commandBuffer.DrawPrimitives(0, 1, 0, 0); | ||||
| 				commandBuffer.DrawPrimitives(0, 1); | ||||
| 
 | ||||
| 				commandBuffer.EndRenderPass(); | ||||
| 
 | ||||
| 				GraphicsDevice.Submit(commandBuffer); | ||||
| 				Device.Submit(commandBuffer); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|  | @ -291,23 +321,20 @@ namespace MoonWorks.Video | |||
| 
 | ||||
| 		private void InitializeDav1dStream() | ||||
| 		{ | ||||
| 			ReadNextFrameTask?.Wait(); | ||||
| 
 | ||||
| 			ResetStreamATask = Task.Run(Video.StreamA.Reset); | ||||
| 			ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 			ResetStreamBTask = Task.Run(Video.StreamB.Reset); | ||||
| 			ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); | ||||
| 			Video.StreamA.Load(); | ||||
| 			Video.StreamB.Load(); | ||||
| 
 | ||||
| 			CurrentStream = Video.StreamA; | ||||
| 			currentFrame = -1; | ||||
| 		} | ||||
| 
 | ||||
| 		private static void HandleTaskException(Task task) | ||||
| 		private void ResetDav1dStream() | ||||
| 		{ | ||||
| 			if (task.Exception.InnerException is not TaskCanceledException) | ||||
| 			{ | ||||
| 				throw task.Exception; | ||||
| 			} | ||||
| 			Video.StreamA.Reset(); | ||||
| 			Video.StreamB.Reset(); | ||||
| 
 | ||||
| 			CurrentStream = Video.StreamA; | ||||
| 			currentFrame = -1; | ||||
| 		} | ||||
| 
 | ||||
| 		protected override void Dispose(bool disposing) | ||||
|  |  | |||
|  | @ -21,6 +21,15 @@ namespace MoonWorks | |||
| 		public bool Claimed { get; internal set; } | ||||
| 		public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; } | ||||
| 
 | ||||
| 		public (int, int) Position | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				SDL.SDL_GetWindowPosition(Handle, out var x, out var y); | ||||
| 				return (x, y); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private bool IsDisposed; | ||||
| 
 | ||||
| 		private static Dictionary<uint, Window> idLookup = new Dictionary<uint, Window>(); | ||||
|  | @ -113,6 +122,14 @@ namespace MoonWorks | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Sets the window position. | ||||
| 		/// </summary> | ||||
| 		public void SetPosition(int x, int y) | ||||
| 		{ | ||||
| 			SDL.SDL_SetWindowPosition(Handle, x, y); | ||||
| 		} | ||||
| 
 | ||||
| 		internal static Window Lookup(uint windowID) | ||||
| 		{ | ||||
| 			return idLookup.ContainsKey(windowID) ? idLookup[windowID] : null; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue