Possible to use PNG textures in DX6?

Started by
8 comments, last by Rhapsodus 20 years, 5 months ago
I was wondering if anyone has a tutorial showing how to use PNGs as textures in directx6< First off I should have probably asked, Is it even possible to do to begin with? I have objects that are xx% transparent using material transparency but it is a lot easier to use texture transparency instead.... These are related questions to my earlier post : http://gamedev.net/community/forums/topic.asp?topic_id=190932
Advertisement
Yes, it is possible. In case of DX6, you have to provide a png loader yourself, though.
LibPNG is an open-source library designed to load and save png images, I recommend you use that (or if adventurous, write your own loader ).
Also, I recommend moving to at least DX7, because IMO it''s much easier to use than 6, as well as more flexible and stable.

-Nik

Niko Suni

Yeah, that is re-affirming to know. I am currently using libpng to load a png by hand. I am also calling CreateDIBitmap to associate the texture's hBitmap to the newly created memory . . . This seemed to work for JPG's and other file
formats.


The problem must be in the way it is rendered... are there special renderstate flags that must be called in order to
render the pngs??

For some reason, all of my pngs loaded with the code below are
rendering as pure black...

You can see the render code here:
http://www.gamedev.net/community/forums/topic.asp?topic_id=190932


Here is how the png's are loaded
//-------------------------------------------------------------------------------------------------// Name: LoadTextureImageAsJPG()// Desc: Loads a texture map and associates ptcTexture->hBitmap to that memory//-------------------------------------------------------------------------------------------------static HRESULT LoadTextureImageAsJPG( HMODULE hArtDLL, TextureContainer* ptcTexture, int nType ){		//-----------------------------------------------------------------------	// Check to make sure the resource exists and lock it, otherwise fail		//-----------------------------------------------------------------------		UINT wd, ht;		LPBYTE pBits ;		TCHAR* strFilename = ptcTexture->strName;		char fname[_MAX_FNAME];		_splitpath( strFilename, NULL, NULL, fname, NULL);		HRSRC hrsrc = FindResource( hArtDLL, fname, "JPG" ) ;		if ( !hrsrc )				return DDERR_NOTFOUND;			HGLOBAL hg = LoadResource( hArtDLL, hrsrc ) ;		if ( !hg )				return DDERR_NOTFOUND;		BYTE *pRes = (BYTE *) LockResource( hg ) ;		if ( !pRes )			return DDERR_NOTFOUND;		//-----------------------------------------------------------------------		// If specified, load the graphic as a PNG file		//-----------------------------------------------------------------------		if( nType == LOAD_PNG )		{				//-----------------------------------------------------------------------				// Hard code the png loading variables for now, change when it works			//-----------------------------------------------------------------------				boolean bHasAlpha = TRUE;				int wd2=111;				int ht2 = 111;				ReadPNG( "C:\\test.png", 0, pBits, wd2, ht2, bHasAlpha );				if ( !pBits)						return DDERR_NOTFOUND;				//-----------------------------------------------------------------------				// Create a device independent bitmap for the texture				//-----------------------------------------------------------------------				BITMAPINFO bmi;				BITMAPINFOHEADER& bmiHeader = bmi.bmiHeader;				bmiHeader.biSize = sizeof(BITMAPINFOHEADER);				bmiHeader.biWidth = wd;				bmiHeader.biHeight = ht;				bmiHeader.biPlanes = 1;					bmiHeader.biBitCount = 32;				bmiHeader.biCompression = BI_PNG;				bmiHeader.biSizeImage = 0;				bmiHeader.biXPelsPerMeter = 0;				bmiHeader.biYPelsPerMeter = 0;				bmiHeader.biClrUsed = 0;				bmiHeader.biClrImportant = 0;				HDC hdc = GetDC(NULL);				ptcTexture->hbmBitmap = CreateDIBitmap(hdc, &bmi.bmiHeader, CBM_INIT,(CONST VOID *)pBits,  &bmi,  DIB_RGB_COLORS);				return DD_OK;		}	/* Read a PNG file.  You may want to return an error code if the read * fails (depending upon the failure).  There are two "prototypes" given * here - one where we are given the filename, and we need to open the * file, and the other where we are given an open file (possibly with * some or all of the magic bytes read - see comments above). */boolean ReadPNG(	const char * filename,	int nLen,	LPBYTE& lpBuffer,	int& width,	int& height,	boolean& bHasAlpha ){	png_structp png_ptr;	png_infop info_ptr;	FILE *fp = NULL;	if (nLen == 0)	{		if ((fp = fopen(filename, "rb")) == NULL)		  return false;	}   /* Create and initialize the png_struct with the desired error handler    * functions.  If you want to use the default stderr and longjump method,    * you can supply NULL for the last three parameters.  We also supply the    * the compiler header file version, so that we know if the application    * was compiled with a compatible version of the library.  REQUIRED    */   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,	   NULL, NULL, NULL);   if (png_ptr == NULL)   {      if (fp != NULL) fclose(fp);      return false;   }   /* Allocate/initialize the memory for image information.  REQUIRED. */   info_ptr = png_create_info_struct(png_ptr);   if (info_ptr == NULL)   {      if (fp != NULL) fclose(fp);      png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);      return false;   }   /* Set error handling if you are using the setjmp/longjmp method (this is    * the normal method of doing things with libpng).  REQUIRED unless you    * set up your own error handlers in the png_create_read_struct() earlier.    */   if (setjmp(png_jmpbuf(png_ptr)))   {      /* Free all of the memory associated with the png_ptr and info_ptr */      png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);      if (fp != NULL) fclose(fp);      /* If we get here, we had a problem reading the file */      return false;   }   /* One of the following I/O initialization methods is REQUIRED */	if (nLen == 0)	{		/* Set up the input control if you are using standard C streams */		png_init_io(png_ptr, fp);	}	else	{		/* If you are using replacement read functions, instead of calling		* png_init_io() here you would call:		*/		png_set_read_fn(png_ptr, (void *)filename, user_read_fn);		/* where user_io_ptr is a structure you want available to the callbacks */	}	/* If we have already read some of the signature */	png_set_sig_bytes(png_ptr, 0);	/*	* If you have enough memory to read in the entire image at once,	* and you need to specify only transforms that can be controlled	* with one of the PNG_TRANSFORM_* bits (this presently excludes	* dithering, filling, setting background, and doing gamma	* adjustment), then you can read the entire image (including	* pixels) into the info structure with this call:	*/	png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND|PNG_TRANSFORM_BGR|PNG_TRANSFORM_STRIP_16, NULL);	int		padding = 0;					// Padding at end of each row	lpBuffer = new BYTE[info_ptr->width*info_ptr->height*4];	width = info_ptr->width;	height = info_ptr->height;	png_bytepp rows = png_get_rows(png_ptr, info_ptr);	byte*	pDest = lpBuffer;	byte*	pSrc;	byte	b;						// Used for gray	if (info_ptr->channels == 1)  // gray	{		// rows in RGB		bHasAlpha = false;		for( UINT r=0; r < info_ptr->height; r++)		{			pSrc = rows[r];			for( int c=info_ptr->width; c-- > 0; )			{				*pDest++ = 0xff;				b = *pSrc++;				*pDest++ = b;				*pDest++ = b;				*pDest++ = b;			}			pDest += padding;		}	}	else if (info_ptr->channels == 2)  // gray	{		// rows in RGB		bHasAlpha = false;		for( UINT r=0; r < info_ptr->height; r++)		{			pSrc = rows[r];			for( int c=info_ptr->width; c-- > 0; )			{				*pDest++ = *pSrc++;				b = *pSrc++;				*pDest++ = b;				*pDest++ = b;				*pDest++ = b;			}			pDest += padding;		}	}	else if (info_ptr->channels == 3)	{		// rows in RGB		bHasAlpha = false;		for( UINT r=0; r < info_ptr->height; r++)		{			pSrc = rows[r];			for( int c=info_ptr->width; c-- > 0; )			{				*pDest++ = *pSrc++;				*pDest++ = *pSrc++;				*pDest++ = *pSrc++;				*pDest++ = 0xff;			}			pDest += padding;		}	}	else if (info_ptr->channels == 4)	{		bHasAlpha = true;		for( UINT r=0; r < info_ptr->height; r++)		{			pSrc = rows[r];			memcpy(pDest, pSrc, info_ptr->width*4);			pDest += info_ptr->width*4;			pDest += padding;		}	}	/* At this point you have read the entire image */	/* clean up after the read, and free any memory allocated - REQUIRED */	png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);	/* close the file */	if (fp != NULL) fclose(fp);	/* that's it */	return true;} 



[edited by - Rhapsodus on November 13, 2003 4:03:31 AM]
Step thru this part:
else if (info_ptr->channels == 4)	{	bHasAlpha = true;		for( UINT r=0; r < info_ptr->height; r++){			pSrc = rows[r];memcpy(pDest, pSrc, info_ptr->width*4);pDest += info_ptr->width*4;pDest += padding;		}	}

Verify that the pDest is a valid pointer to the destination surface data (obtained by locking the surf).
Also, verify that the data is actually valid.

To render with alpha, you need to set source and destination blend modes with corresponding renderstates (although if you already use material alpha you know this).
I don't have DX6 docs anymore () so i can't check the actual names of the said renderstates.

-Nik

EDIT: Failing to unlock the surface may also be related to the black result.
This would be a driver bug; if it behaved correctly, it would create an access violation as the hardware would touch the locked data.

EDIT2: In loading DDraw surfaces, the correct way is to copy the data one scanline at a time, and increment the the surface pointer by the pitch (scanline memory width) after each one. The (surface's displayable width * bitsPerPixel / 8) may not equal it's pitch, because of caching.

[edited by - Nik02 on November 13, 2003 4:33:55 AM]

Niko Suni


Good idea,

Although to be safe I hard coded every pixel to the same color, hoping that the entire world would be some color;

	if( nType == LOAD_PNG )	{		//********************************************************************		// Hard code the png loading variables for now, change when it works		//********************************************************************		boolean bHasAlpha = TRUE;		int wd2=111;		int ht2 = 111;		ReadPNG( "C:\\test.png", 0, pBits, wd2, ht2, bHasAlpha );				if ( !pBits)			return DDERR_NOTFOUND;		//********************************************************************		// Create a device independent bitmap for the texture		//********************************************************************		BITMAPINFO bmi;		BITMAPINFOHEADER& bmiHeader = bmi.bmiHeader;		bmiHeader.biSize = sizeof(BITMAPINFOHEADER);		bmiHeader.biWidth = wd;		bmiHeader.biHeight = ht;		bmiHeader.biPlanes = 1;		bmiHeader.biBitCount = 32;		bmiHeader.biCompression = BI_PNG;		bmiHeader.biSizeImage = wd*ht*4;		bmiHeader.biXPelsPerMeter = 0;		bmiHeader.biYPelsPerMeter = 0;		bmiHeader.biClrUsed = 0;		bmiHeader.biClrImportant = 0;				for(int y=0; y<111; y++)		{			for(int x=0; x<111; x++ )			{				int *pbyte = NULL;				(int*)pbyte = (int*)(pBits + (y*(111*4) + x*4));								int r = RedValue(*pbyte);				int g = GreenValue(*pbyte);				int b = BlueValue(*pbyte);				int a = AlphaValue(*pbyte);								if( a != 0 )				{					*pbyte = MakePixel( 215, 230, 40, 40 );				}			}		}						HDC hdc = GetDC(NULL);		ptcTexture->hbmBitmap = CreateDIBitmap(hdc, &bmi.bmiHeader, CBM_INIT,(CONST VOID *)pBits,  &bmi,  DIB_RGB_COLORS);		//ptcTexture->ptexTexture->Load		return DD_OK;	}



I am also using the following rendering States, on the mesh::render function

		//********************************************************************		// Not working?? Force all object to blend alpha		//********************************************************************	    pd3dDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, TRUE);		pd3dDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_SRCALPHA );		pd3dDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_INVSRCALPHA );		//pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_BLENDTEXTUREALPHA);  		//pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,  D3DTA_DIFFUSE);		//********************************************************************		// Go through the textures, and draw the primitives		//********************************************************************		for(n = 0;n<groups.GetSize();n++)		{			CDXFileGroup *gp = &groups[ n ] ;			//********************************************************************			// If the texture isn''t set, try to find the texture in the tree			//********************************************************************			if ( gp->m_Texture == NULL && gp->m_strTexture[ 0 ] != ''\0'' )			{				gp->m_Texture = D3DTextr_GetTexture( gp->m_strTexture ) ;			}			//********************************************************************			// Set the actual texture, and attempt to draw			//********************************************************************			pd3dDevice->SetTexture( 0, gp->m_Texture ) ;			try			{				pd3dDevice->DrawIndexedPrimitiveVB( D3DPT_TRIANGLELIST, lpD3DVertexBuffer2,												m_pIndices+groups[n].from*3, groups[n].count*3, 0 );			}			catch(...)			{					//********************************************************************					// Something major went wrong, abort					//********************************************************************					g_pKillMe = this;					return;			}		}	}	pd3dDevice->SetTexture( 0, NULL ) ;		//********************************************************************	// Render the next mesh in the tree	//********************************************************************	if( m_pNext )	{		m_pNext->Render( pd3dDevice, pmtrl ,transp_type,r_flag);	}



The wierd thing is, that it still renders as fully black after hard coding every pixel. When I stepped through and looked at every pixel they seemed to contain the right colors...

pDest was a valid pointer.

I never had to call a specific lock function on any surface for rendering, with bmp files or with jpgs? I am just setting a texture and drawing the indexedprimitive

I am simply creating a DIBBitmap and setting the texture''s handle to point to the new memory which should contain a big block of ARGB pixels (4 bytes each).

I guess I am not asking the right question, i think the real question is:


How does one associate a texture & surface pointer to a block of
memory representing pixels in the format: ARGB in older versions of directX ?

For example:

  //Create block of memory for pixel effects  byte *pImageMemory = new byte[ 100 * 100 * 4 ]; // Set up a surface and texture from this memory LPDIRECTDRAWSURFACE4 pddsSurface;   LPDIRECT3DTEXTURE2   ptexTexture;  


How do you get a surface and texture associated to the memory above??

To access IDirectDrawSurface4's memory, there are lock and unlock methods for accessing the data.
They acquire and release the surface data pointer from the driver, respectively.

Now, i don't remember how IDirect3DTexture was exactly implemented, but i seem to recall that somehow you could query the texture for it's surfaces.

Therefore:
-Create an empty texture (i don't remember how in 6)
-Query it for a surface interface
-Lock the surface
-Use the pointer you're given by locking it to fill it
-Unlock the surface
-Release the surface interface
-Texture should be ready to use.

Sorry i can't be more specific, i don't really remember dx6 things clearly anymore

-Nik

EDIT:
CreateTexture / CreateSurface methods allocate the memory outside of your program.
I don't think it's strictly allowed to "associate" your memory area as a surface.
But this is what locking is for, it lets you access driver-allocated memory.


[edited by - Nik02 on November 14, 2003 7:53:08 AM]

Niko Suni

DX6? I thought it went from 5 to 7.

Hmm... anyway, you should be using 9 now.
I do not want to use directx9 because of older machines and older cards, it would kill compatability...

It WOULD be nice to use that CreateTextureFromFile function though

I think locking the surface and modifying the bits directly is the right way to do this, thanks to Nik02.


It is hard to tell what memory directx has allocated though, ie: the size of an LPVOID is undefined

		DDSURFACEDESC2 ddSurfaceDesc;		D3DUtil_InitSurfaceDesc( ddSurfaceDesc );		g_pFramework->GetRenderSurface()->GetSurfaceDesc( &ddSurfaceDesc );		ddSurfaceDesc.dwFlags         	= 	DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH;		ddSurfaceDesc.ddsCaps.dwCaps  	= 	DDPF_ALPHAPIXELS;		ddSurfaceDesc.dwWidth         	= 	111;		ddSurfaceDesc.dwHeight        	= 	111;		ddSurfaceDesc.dwSize = sizeof(ddSurfaceDesc);		if( !ptcTexture->pddsSurface || !ptcTexture->ptexTexture )		{			HRESULT hr;						if( FAILED( hr = g_pFramework->GetDirectDraw()->CreateSurface( &ddSurfaceDesc, &ptcTexture->pddsSurface, NULL ) ) )			{				return DDERR_NOTFOUND;			}			if( FAILED( ptcTexture->pddsSurface->QueryInterface( IID_IDirect3DTexture2, (VOID**)&ptcTexture->ptexTexture ) ) )			{				return DDERR_NOTFOUND;			}		}		if( ptcTexture->pddsSurface->Lock( NULL, &ddSurfaceDesc, DDLOCK_NOSYSLOCK | DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL) == DD_OK )		{						ReadPNG( "C:\\test.png", 0, (LPBYTE&)ddSurfaceDesc.lpSurface, wd2, ht2, bHasAlpha );			if( ptcTexture->pddsSurface->Unlock( NULL ) != DD_OK )			{				return DDERR_NOTFOUND;			}		}


I don''t have it working it, directx locks up the machine now, but I think this is the right way to go with a little tweaking
I have to adapt it to my current TextureContainer

quote:Original post by Rhapsodus
I think locking the surface and modifying the bits directly is the right way to do this, thanks to Nik02.


Correct

quote:
It is hard to tell what memory directx has allocated though, ie: the size of an LPVOID is undefined


But surely you know how much bytes a pixel contains?
Cast the pointer to a "pixel" (for example DWORD*, WORD* or BYTE* for 32-, 16- or 8-bit pixels respectively). This allows to treat the surface data as your own memory and to manipulate it directly, even though it may reside in the driver.
You can look at the surface descriptor for info on surface's pitch, width, height, bpp and such.

EDIT:
Lock-ups on dx usually occur if you write past locked memory.
This potentially creates a kernel-level access violation, which, if you're lucky, results in blue screens or crashes.
Check the code in where you copy the data for logic errors.


[edited by - Nik02 on November 15, 2003 4:35:44 AM]

Niko Suni

This topic is closed to new replies.

Advertisement