Skip to main content
GameDev.net gamedev.net
🔒 Locked 🤖 Godot

Issue passing a texture to shader.

Started by PumpkinPieman Feb 9, 2009 at 12:23 AM 14 replies 6.7k views
Original Post
PumpkinPieman
PumpkinPieman
I tried posting this on the XNA community forums but I got no such luck, just questions as to what I'm attempting. Now, my problem here is that I'm creating a texture, filling it full of noise. Then passing it to the shader as a parameter, however the shader won't sample the texture and return anything but black. I know the texture has been set correctly, I've read back the data and it's the same I put in it. But that doesn't explain why when I pass it the shader acts as if it wasn't set and just returns nothing...

private void UpdateSurfaceNoiseGPU(GameTime gameTime)
		{
			if (generateList.Count > 0 == false)
				return;

			// device
			GraphicsDevice device = game.GraphicsDevice;
			Vector2 viewportSize = new Vector2(blockWidth, blockHeight);
			
			// noise info
			Vector4 noiseInfo = generateList[0];
			int noiseWidth = (int)noiseInfo.X;
			int noiseHeight = (int)noiseInfo.Y;

			Texture2D noiseTexture = new Texture2D(device, noiseWidth, noiseHeight, 0, TextureUsage.None, SurfaceFormat.Color);
			Color[] noiseData = new Color[noiseWidth * noiseHeight];

			for(int x = 0; x < noiseWidth; x++)
			{
				for(int y = 0; y < noiseHeight; y++)
				{
					noiseData[x + y * noiseWidth] = new Color( (byte)(Util.ImageProcessing.PerlinNoise.Noise2D(x, y) * 255), 0, 0, 0 );
				}
			}

			noiseTexture.SetData<Color>(noiseData);

			// setup shader constants
			shaderGenerate.Parameters["ViewportSize"].SetValue(viewportSize);
			shaderGenerate.Parameters["Noise"].SetValue(noiseTexture);

			// draw on to surface
			for (UInt32 x = 0; x < mapBlockWidthNum; x++)
			{
				for (UInt32 y = 0; y < mapBlockHeightNum; y++)
				{
					TerrainBlock block = mapBlocks[x, y];
						// set the render target, draw and swap
					device.SetRenderTarget(0, mapSwapBlock);

					batch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
					shaderGenerate.Begin();
					shaderGenerate.CurrentTechnique.Passes[0].Begin();

					batch.Draw(block.SurfaceTexture, Vector2.Zero, Color.White);

					shaderGenerate.CurrentTechnique.Passes[0].End();
					shaderGenerate.End();
					batch.End();

					device.SetRenderTarget(0, null);

					FlipBlockBackBuffer(block);
				}
			}
			generateList.Clear();
			noiseTexture.Dispose();
			device.SetRenderTarget(0, null);
}
The problem exists here: shaderGenerate.Parameters["Noise"].SetValue(noiseTexture); The shader I'm using is a modified version of the SpriteBatch shader, which works in my other process for terrain painting but not this.

// SpriteBatch default variables

texture	Noise;
sampler	NoiseSampler = sampler_state { Texture = <Noise>; MipFilter = Linear; MinFilter = Linear; MagFilter = Linear; AddressU = Clamp; AddressV  = Clamp; };

// spritebatch vertex code...

void SpritePixelShader(inout float4 color : COLOR0, float2 texCoord : TEXCOORD0)
{
	float4 surface	= tex2D(TextureSampler, texCoord);
	float4 noise	= tex2D(NoiseSampler, texCoord);
	color = noise;
}
Now, I must be doing something completely wrong. Because my other method works and is similar, I can even pass in textures to the shader. I'm not sure where to approach this because this texture needs to be a shader parameter and not passed in with the Draw call..
MJP
MJP
Does everything still work correctly if you have your pixel shader sample from TextureSampler? What happens if you send your noise texture as the parameter for SpriteBatch.Draw and sample from TextureSampler, does it still show up? This will verify that you're filling up your noise texture with correct values. You could also verify by running your app in PIX, finding your texture, and viewing it.

If you've made sure your noise texture is coming out directly, you'll want to do some further debugging. Run your app with the debug runtimes active, and make sure you're not getting any errors. Then after that I would have a look at the native D3D9 calls that are happening behind the scene. Find the call to IDirect3DDevice9::SetTexture that's setting your noise texture to the device. Then look through the calls leading up to the actual DrawPrimitive call, and make sure your Effect is being applied properly and that the SpriteBatch is messing up anything for you.
PumpkinPieman
PumpkinPieman
Listen, I know this is not much. But this shouldn't happen. I've tried two different methods. I've tried creating this texture and I've tried using an existing rendertarget and drawing something on it through a pixel shader. Both methods seem to yield no results...

Now, here is the thing that really drives me mad. If I load a texture I've created outside of runtime it works fine.

Without Terrain Generation: (Red: 1, Blue: 0, Green: 0, Height: ~0)
Free Image Hosting at www.ImageShack.us

Terrain With Generation: (Red: 0, Blue: 0, Green: 0, Height: 0) <- this should have a red layer of noise.
// note this uses the noise texture which I have checked that was created perfectly.........
Free Image Hosting at www.ImageShack.us

Terrain with Generation: (Red: tex.r, Blue: tex.b, Green: tex.g, Height: tex.a)
// note this uses a texture of the same size that I pass using the same parameter as the noise, no other code has been changed
Free Image Hosting at www.ImageShack.us

What the heck is going wrong with this application? It can't be the shader, it draws the parameter fine. It can't be the shader object because it passes the texture fine. And it can't be the texture because I've read back the data. It's set perfectly, and even uses base 2.

Please, anything will help...


EDIT:
Quote:
Original post by MJP
Does everything still work correctly if you have your pixel shader sample from TextureSampler? What happens if you send your noise texture as the parameter for SpriteBatch.Draw and sample from TextureSampler, does it still show up? This will verify that you're filling up your noise texture with correct values. You could also verify by running your app in PIX, finding your texture, and viewing it.

If you've made sure your noise texture is coming out directly, you'll want to do some further debugging. Run your app with the debug runtimes active, and make sure you're not getting any errors. Then after that I would have a look at the native D3D9 calls that are happening behind the scene. Find the call to IDirect3DDevice9::SetTexture that's setting your noise texture to the device. Then look through the calls leading up to the actual DrawPrimitive call, and make sure your Effect is being applied properly and that the SpriteBatch is messing up anything for you.


Thanks for your response. Yes, it does work fine. However, what I'm planning on attempting is not just simple terrain generation. I also want to have the ability to generate noise on the terrain by a factor of x. Which would give me the ability to create rippes in the terrain.

As for the second part. I've been trying to use PIX to debug it, but it has been no help. The first thing I would have suspected is as you say. However, I'm able to pass a texture loaded by the content manager in. Which boggles my mind...
MJP
MJP
Can you check to see which resource pool your noise texture was created in? I would think XNA would place in D3DPOOL_MANAGED, but if it wasn't it could cause problems.
MJP
MJP
Easiest way to do it would be to just make it a size that you'll easily recognize. Then in the PIX resources view just filter by that size, and your texture and surface will show up right away. Then you can view the texture to make sure it has the right data, and view the resource pool and other properties.
PumpkinPieman
PumpkinPieman
In PIX when you take a frame capture does this capture the frame from the begining of when the backbuffer is drawn to when it is flipped?
MJP
MJP
Yes, it gets everything from the last time you called Present
MJP
MJP
Hmm...I'm out of ideas then I'm afraid. When I get home from work I'll see if I can use your app code and shader code to reproduce this behavior.
PumpkinPieman
PumpkinPieman
F*@k.

I figured out this issue. Now I need a way around it...

The issue is exactly this. DirectX doesn't support reading textures larger then the rendertarget. Seeing as my rendertargets are 128x128, and my texture is 256x256 that would make sense.

However.

I can import a brush in to by other pixel shader through constants and it draws fine. The real reason why this works? Well, I loaded this through the Content manager. I don't know how this really changes anything... but it seems this only works if the resource is < rendertarget size || created by the Content manager.

I tried setting the rendertarget to the backbuffer, but this didn't help. Which made me think that I am setting the texture parameters incorrect. My brush is larger then the surface so it can't be a size restriction so it must be a texture creation issue.

Any ideas?

EDIT: TextureUseage.AutoGemerateMipMap works. Why wouldn't TexureUseage.None work in this situation?
MJP
MJP
Ahhh okay it all makes perfect sense now.

You were getting black because you only filled the top mip-level of your texture. Since you specified 0 as the number of mip levels, your texture gets a full mipmap chain. However your SetData call only fills in the top level. Then when you went to actually draw the texture, since your were scaling it down to a smaller size the device sampled from one of your lower mip levels and not the top-most level, which means it came back with uninitialized texture data.

The reason it works when you use AutoGenMipMaps, is because the device automatically fills in the lower mip levels for you when you call SetData.

The reason it works when you use the ContentManager is because the Texture ContentProcessor automatically generates a full mipmap chain for you (by default).

By the way...using a texture larger than the render target is completely valid in D3D.
PumpkinPieman
PumpkinPieman
Wow, awesome. Thanks for the info. It's good to know. Now here is my question. Some of the textures I use, I don't want a texture chain...

On the XBOX I want to reduce as much memory as possible and some of these mipmaps really aren't needed. Is there any way to not have these?
MJP
MJP
Yes, just pass "1" as the value of the "numberLevels" parameter of the Texture2D constructor. If you don't want mipmaps for any textures that you're loading via the Content Pipeline, go to the Properties for that texture and expand the Content Processor node. Then set "Generate Mipmaps" to false.
PumpkinPieman
PumpkinPieman
Awesome. You've been a great help to me. Rate++;

One more question that is off topic. Is there a way to directly call Load functions on resources that get called through the pipeline? There are a few things that I don't like about using the Content manager and I'd like to develop a class that doesn't just extend the functionality of the class but gives some new methods for content management.
MJP
MJP
Yes, if you inherit from ContentManager you can do all sorts of customization. See this.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.