[Test] public void RenderAndDispose() { PixelData pixelData; // In this test, we'll dispose the render target immediately after using it // and only retrieve the pixel data from its bound texture later on. // Since the texture that was rendered to is still alive, this should work. using (Texture texture = new Texture(8, 8, TextureSizeMode.NonPowerOfTwo, TextureMagFilter.Nearest, TextureMinFilter.Nearest)) { using (RenderTarget renderTarget = new RenderTarget(AAQuality.High, false, texture)) using (DrawDevice device = new DrawDevice()) { device.Projection = ProjectionMode.Screen; device.VisibilityMask = VisibilityFlag.AllGroups | VisibilityFlag.ScreenOverlay; device.Target = renderTarget; device.TargetSize = renderTarget.Size; device.ViewportRect = new Rect(renderTarget.Size); device.PrepareForDrawcalls(); device.AddVertices(Material.SolidWhite, VertexMode.Quads, new VertexC1P3 { Pos = new Vector3(0, 0, 0), Color = ColorRgba.Red }, new VertexC1P3 { Pos = new Vector3(0, device.TargetSize.Y, 0), Color = ColorRgba.Red }, new VertexC1P3 { Pos = new Vector3(device.TargetSize.X, device.TargetSize.Y, 0), Color = ColorRgba.Red }, new VertexC1P3 { Pos = new Vector3(device.TargetSize.X, 0, 0), Color = ColorRgba.Red }); device.Render(); } pixelData = texture.GetPixelData(); } Assert.IsTrue(this.IsFilledWithColor(pixelData, ColorRgba.Red)); }
private void RecreateTexturedBackground(TileSet levelTileset, ref TileMapLayer layer) { int w = layer.LayoutWidth; int h = layer.Layout.Length / w; Texture targetTexture; if (cachedTexturedBackground.IsAvailable) { targetTexture = cachedTexturedBackground.Res; } else { targetTexture = new Texture(w * 32, h * 32, TextureSizeMode.NonPowerOfTwo, TextureMagFilter.Linear, TextureMinFilter.Linear, TextureWrapMode.Repeat, TextureWrapMode.Repeat); } using (DrawDevice device = new DrawDevice()) { device.VisibilityMask = VisibilityFlag.AllFlags; device.Projection = ProjectionMode.Screen; using (RenderTarget target = new RenderTarget(AAQuality.Off, false, targetTexture)) { device.Target = target; device.TargetSize = new Vector2(w * 32, h * 32); device.ViewportRect = new Rect(device.TargetSize); device.PrepareForDrawcalls(); // ToDo Material material = levelTileset.GetDefaultTile(0).Material.Res; Texture texture = material.MainTexture.Res; // Reserve the required space for vertex data in our locally cached buffer int neededVertices = 4 * w * h; if (cachedVertices == null || cachedVertices.Length < neededVertices) { cachedVertices = new VertexC1P3T2[neededVertices]; } int vertexIndex = 0; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { LayerTile tile = layer.Layout[x + y * layer.LayoutWidth]; if (tile.IsAnimated) { continue; } Point2 offset = tile.MaterialOffset; bool isFlippedX = tile.IsFlippedX; bool isFlippedY = tile.IsFlippedY; Rect uvRect = new Rect( offset.X * texture.UVRatio.X / texture.ContentWidth, offset.Y * texture.UVRatio.Y / texture.ContentHeight, levelTileset.TileSize * texture.UVRatio.X / texture.ContentWidth, levelTileset.TileSize * texture.UVRatio.Y / texture.ContentHeight ); if (isFlippedX) { uvRect.X += uvRect.W; uvRect.W *= -1; } if (isFlippedY) { uvRect.Y += uvRect.H; uvRect.H *= -1; } Vector3 renderPos = new Vector3(x * 32, y * 32, 0); renderPos.X = MathF.Round(renderPos.X); renderPos.Y = MathF.Round(renderPos.Y); if (MathF.RoundToInt(device.TargetSize.X) != (MathF.RoundToInt(device.TargetSize.X) / 2) * 2) { renderPos.X += 0.5f; } if (MathF.RoundToInt(device.TargetSize.Y) != (MathF.RoundToInt(device.TargetSize.Y) / 2) * 2) { renderPos.Y += 0.5f; } Vector2 tileXStep = new Vector2(32, 0); Vector2 tileYStep = new Vector2(0, 32); cachedVertices[vertexIndex + 0].Pos.X = renderPos.X; cachedVertices[vertexIndex + 0].Pos.Y = renderPos.Y; cachedVertices[vertexIndex + 0].Pos.Z = renderPos.Z; cachedVertices[vertexIndex + 0].TexCoord.X = uvRect.X; cachedVertices[vertexIndex + 0].TexCoord.Y = uvRect.Y; cachedVertices[vertexIndex + 0].Color = ColorRgba.White; cachedVertices[vertexIndex + 1].Pos.X = renderPos.X + tileYStep.X; cachedVertices[vertexIndex + 1].Pos.Y = renderPos.Y + tileYStep.Y; cachedVertices[vertexIndex + 1].Pos.Z = renderPos.Z; cachedVertices[vertexIndex + 1].TexCoord.X = uvRect.X; cachedVertices[vertexIndex + 1].TexCoord.Y = uvRect.Y + uvRect.H; cachedVertices[vertexIndex + 1].Color = ColorRgba.White; cachedVertices[vertexIndex + 2].Pos.X = renderPos.X + tileXStep.X + tileYStep.X; cachedVertices[vertexIndex + 2].Pos.Y = renderPos.Y + tileXStep.Y + tileYStep.Y; cachedVertices[vertexIndex + 2].Pos.Z = renderPos.Z; cachedVertices[vertexIndex + 2].TexCoord.X = uvRect.X + uvRect.W; cachedVertices[vertexIndex + 2].TexCoord.Y = uvRect.Y + uvRect.H; cachedVertices[vertexIndex + 2].Color = ColorRgba.White; cachedVertices[vertexIndex + 3].Pos.X = renderPos.X + tileXStep.X; cachedVertices[vertexIndex + 3].Pos.Y = renderPos.Y + tileXStep.Y; cachedVertices[vertexIndex + 3].Pos.Z = renderPos.Z; cachedVertices[vertexIndex + 3].TexCoord.X = uvRect.X + uvRect.W; cachedVertices[vertexIndex + 3].TexCoord.Y = uvRect.Y; cachedVertices[vertexIndex + 3].Color = ColorRgba.White; vertexIndex += 4; } } device.AddVertices(material, VertexMode.Quads, cachedVertices, 0, vertexIndex); device.Render(); } } cachedTexturedBackground = targetTexture; }
private void ProcessCombineSceneStep(DrawDevice drawDevice) { // ToDo: Split lighting to RGB channels // ToDo: Implement dynamic lighting/shadows (https://github.com/mattdesl/lwjgl-basics/wiki/2D-Pixel-Perfect-Shadows) Vector2 viewSize = drawDevice.TargetSize; Vector2 viewOffset = new Vector2( drawDevice.RefCoord.X - viewSize.X / 2, drawDevice.RefCoord.Y - viewSize.Y / 2 ); float ambientLight = levelHandler.AmbientLightCurrent; float viewWaterLevel = (levelHandler.WaterLevel - viewOffset.Y); // Blit ambient light color { BatchInfo material = drawDevice.RentMaterial(); material.Technique = DrawTechnique.Solid; material.MainColor = new ColorRgba(ambientLight, 0, 0); this.Blit(drawDevice, material, lightingTarget); } // Render lights (target was set in previous step) drawDevice.PrepareForDrawcalls(); foreach (GameObject actor in levelHandler.ActiveObjects) { LightEmitter light = actor.GetComponent <LightEmitter>(); if (light != null) { // World-space to screen-space position transformation Vector3 pos = actor.Transform.Pos; pos.X -= viewOffset.X; pos.Y -= viewOffset.Y; float left = pos.X - light.RadiusFar; float top = pos.Y - light.RadiusFar; float right = pos.X + light.RadiusFar; float bottom = pos.Y + light.RadiusFar; if (left < viewSize.X && top < viewSize.Y && right > 0 && bottom > 0) { lightBuffer[0].Pos.X = left; lightBuffer[0].Pos.Y = top; lightBuffer[1].Pos.X = left; lightBuffer[1].Pos.Y = bottom; lightBuffer[2].Pos.X = right; lightBuffer[2].Pos.Y = bottom; lightBuffer[3].Pos.X = right; lightBuffer[3].Pos.Y = top; // Use TexCoord X & Y for screen-space Light position lightBuffer[0].TexCoord.X = lightBuffer[1].TexCoord.X = lightBuffer[2].TexCoord.X = lightBuffer[3].TexCoord.X = pos.X; lightBuffer[0].TexCoord.Y = lightBuffer[1].TexCoord.Y = lightBuffer[2].TexCoord.Y = lightBuffer[3].TexCoord.Y = pos.Y; // Use TexCoord Z & W for Light radius lightBuffer[0].TexCoord.Z = lightBuffer[1].TexCoord.Z = lightBuffer[2].TexCoord.Z = lightBuffer[3].TexCoord.Z = light.RadiusNear; lightBuffer[0].TexCoord.W = lightBuffer[1].TexCoord.W = lightBuffer[2].TexCoord.W = lightBuffer[3].TexCoord.W = light.RadiusFar; // Use Red channel for Light intensity lightBuffer[0].Color.R = lightBuffer[1].Color.R = lightBuffer[2].Color.R = lightBuffer[3].Color.R = (byte)(light.Intensity * 255); // Use Green channel for Light brightness lightBuffer[0].Color.G = lightBuffer[1].Color.G = lightBuffer[2].Color.G = lightBuffer[3].Color.G = (byte)(light.Brightness * 255); switch (light.Type) { default: case LightType.Solid: drawDevice.AddVertices(lightingMaterial, VertexMode.Quads, lightBuffer); break; case LightType.WithNoise: drawDevice.AddVertices(lightingNoiseMaterial, VertexMode.Quads, lightBuffer); break; } } } } drawDevice.Render(); // Resize Blur targets SetupTargets((Point2)drawDevice.TargetSize); // Blit it into screen { BatchInfo material = drawDevice.RentMaterial(); material.Technique = DrawTechnique.Solid; material.MainTexture = mainTexture; this.Blit(drawDevice, material, targetPingPongA[0]); } // Downsample to lowest target for (int i = 1; i < targetPingPongA.Length; i++) { BatchInfo material = drawDevice.RentMaterial(); material.Technique = downsampleShader; material.MainTexture = targetPingPongA[i - 1].Targets[0]; material.SetValue("pixelOffset", new Vector2(1f / material.MainTexture.Res.ContentWidth, 1f / material.MainTexture.Res.ContentHeight)); this.Blit(drawDevice, material, targetPingPongA[i]); } // Blur all targets, separating horizontal and vertical blur for (int i = 0; i < targetPingPongA.Length; i++) { BatchInfo material = drawDevice.RentMaterial(); material.Technique = blurShader; material.MainTexture = targetPingPongA[i].Targets[0]; material.SetValue("blurDirection", new Vector2(1f, 0f)); material.SetValue("pixelOffset", new Vector2(1f / material.MainTexture.Res.ContentWidth, 1f / material.MainTexture.Res.ContentHeight)); this.Blit(drawDevice, material, targetPingPongB[i]); material.MainTexture = targetPingPongB[i].Targets[0]; material.SetValue("blurDirection", new Vector2(0f, 1f)); material.SetValue("pixelOffset", new Vector2(1f / material.MainTexture.Res.ContentWidth, 1f / material.MainTexture.Res.ContentHeight)); this.Blit(drawDevice, material, targetPingPongA[i]); } // Blit it into screen if (viewWaterLevel < viewSize.Y) { // Render lighting with water BatchInfo material = drawDevice.RentMaterial(); material.Technique = combineSceneWaterShader; material.SetTexture("mainTex", mainTexture); material.SetTexture("lightTex", lightingTexture); material.SetTexture("displacementTex", noiseTexture); // Underwater displacement material.SetTexture("blurHalfTex", targetPingPongA[1].Targets[0]); material.SetTexture("blurQuarterTex", targetPingPongA[2].Targets[0]); material.SetValue("ambientLight", ambientLight); material.SetValue("darknessColor", levelHandler.DarknessColor); material.SetValue("waterLevel", viewWaterLevel / viewSize.Y); this.Blit(drawDevice, material, finalTarget); } else { // Render lighting without water BatchInfo material = drawDevice.RentMaterial(); material.Technique = combineSceneShader; material.SetTexture("mainTex", mainTexture); material.SetTexture("lightTex", lightingTexture); material.SetTexture("blurHalfTex", targetPingPongA[1].Targets[0]); material.SetTexture("blurQuarterTex", targetPingPongA[2].Targets[0]); material.SetValue("ambientLight", ambientLight); material.SetValue("darknessColor", levelHandler.DarknessColor); this.Blit(drawDevice, material, finalTarget); } }
private void RecreateTexturedBackground(ref TileMapLayer layer) { int w = layer.LayoutWidth; int h = layer.Layout.Length / w; cachedTexturedBackgroundAnimated = false; Texture renderTarget; if (cachedTexturedBackground != null) { renderTarget = cachedTexturedBackground.Res; } else { renderTarget = new Texture(w * 32, h * 32, TextureSizeMode.NonPowerOfTwo, TextureMagFilter.Linear, TextureMinFilter.Linear, TextureWrapMode.Repeat, TextureWrapMode.Repeat); switch (layer.BackgroundStyle) { case BackgroundStyle.Sky: default: texturedBackgroundShader = ContentResolver.Current.RequestShader("TexturedBackground"); break; case BackgroundStyle.Circle: texturedBackgroundShader = ContentResolver.Current.RequestShader("TexturedBackgroundCircle"); break; } } using (DrawDevice device = new DrawDevice()) { device.VisibilityMask = VisibilityFlag.AllFlags; device.RenderMode = RenderMatrix.ScreenSpace; device.Target = new RenderTarget(AAQuality.Off, false, renderTarget); device.TargetSize = new Vector2(w * 32, h * 32); device.ViewportRect = new Rect(device.TargetSize); device.PrepareForDrawcalls(); Material material = null; Texture texture = null; // Reserve the required space for vertex data in our locally cached buffer int neededVertices = 4 * w * h; VertexC1P3T2[] vertexData = new VertexC1P3T2[neededVertices]; int vertexBaseIndex = 0; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { LayerTile tile = layer.Layout[x + y * layer.LayoutWidth]; Point2 offset; bool isFlippedX, isFlippedY; if (tile.IsAnimated) { if (tile.TileID < animatedTiles.Count) { offset = animatedTiles[tile.TileID].CurrentTile.MaterialOffset; isFlippedX = (animatedTiles[tile.TileID].CurrentTile.IsFlippedX != tile.IsFlippedX); isFlippedY = (animatedTiles[tile.TileID].CurrentTile.IsFlippedY != tile.IsFlippedY); cachedTexturedBackgroundAnimated = true; } else { continue; } } else { offset = tile.MaterialOffset; isFlippedX = tile.IsFlippedX; isFlippedY = tile.IsFlippedY; } if (material != tile.Material) { // Submit all the vertices as one draw batch device.AddVertices( material, VertexMode.Quads, vertexData, 0, vertexBaseIndex); vertexBaseIndex = 0; material = tile.Material.Res; texture = material.MainTexture.Res; } Rect uvRect = new Rect( offset.X * texture.UVRatio.X / texture.ContentWidth, offset.Y * texture.UVRatio.Y / texture.ContentHeight, tileset.TileSize * texture.UVRatio.X / texture.ContentWidth, tileset.TileSize * texture.UVRatio.Y / texture.ContentHeight ); if (isFlippedX) { uvRect.X += uvRect.W; uvRect.W *= -1; } if (isFlippedY) { uvRect.Y += uvRect.H; uvRect.H *= -1; } Vector3 renderPos = new Vector3(x * 32, y * 32, 0); float scale = 1.0f; device.PreprocessCoords(ref renderPos, ref scale); renderPos.X = MathF.Round(renderPos.X); renderPos.Y = MathF.Round(renderPos.Y); if (MathF.RoundToInt(device.TargetSize.X) != (MathF.RoundToInt(device.TargetSize.X) / 2) * 2) { renderPos.X += 0.5f; } if (MathF.RoundToInt(device.TargetSize.Y) != (MathF.RoundToInt(device.TargetSize.Y) / 2) * 2) { renderPos.Y += 0.5f; } Vector2 tileXStep = new Vector2(32, 0); Vector2 tileYStep = new Vector2(0, 32); vertexData[vertexBaseIndex + 0].Pos.X = renderPos.X; vertexData[vertexBaseIndex + 0].Pos.Y = renderPos.Y; vertexData[vertexBaseIndex + 0].Pos.Z = renderPos.Z; vertexData[vertexBaseIndex + 0].TexCoord.X = uvRect.X; vertexData[vertexBaseIndex + 0].TexCoord.Y = uvRect.Y; vertexData[vertexBaseIndex + 0].Color = ColorRgba.White; vertexData[vertexBaseIndex + 1].Pos.X = renderPos.X + tileYStep.X; vertexData[vertexBaseIndex + 1].Pos.Y = renderPos.Y + tileYStep.Y; vertexData[vertexBaseIndex + 1].Pos.Z = renderPos.Z; vertexData[vertexBaseIndex + 1].TexCoord.X = uvRect.X; vertexData[vertexBaseIndex + 1].TexCoord.Y = uvRect.Y + uvRect.H; vertexData[vertexBaseIndex + 1].Color = ColorRgba.White; vertexData[vertexBaseIndex + 2].Pos.X = renderPos.X + tileXStep.X + tileYStep.X; vertexData[vertexBaseIndex + 2].Pos.Y = renderPos.Y + tileXStep.Y + tileYStep.Y; vertexData[vertexBaseIndex + 2].Pos.Z = renderPos.Z; vertexData[vertexBaseIndex + 2].TexCoord.X = uvRect.X + uvRect.W; vertexData[vertexBaseIndex + 2].TexCoord.Y = uvRect.Y + uvRect.H; vertexData[vertexBaseIndex + 2].Color = ColorRgba.White; vertexData[vertexBaseIndex + 3].Pos.X = renderPos.X + tileXStep.X; vertexData[vertexBaseIndex + 3].Pos.Y = renderPos.Y + tileXStep.Y; vertexData[vertexBaseIndex + 3].Pos.Z = renderPos.Z; vertexData[vertexBaseIndex + 3].TexCoord.X = uvRect.X + uvRect.W; vertexData[vertexBaseIndex + 3].TexCoord.Y = uvRect.Y; vertexData[vertexBaseIndex + 3].Color = ColorRgba.White; vertexBaseIndex += 4; } } device.AddVertices( material, VertexMode.Quads, vertexData, 0, vertexBaseIndex); device.Render(); } cachedTexturedBackground = renderTarget; }