cocos2d for iPhone

A fast, easy to use, free, and community supported 2D game engine

Register or log in - lost password?
  • Blog
  • Store
  • Games
  • Documentation
  • Download
  • About

cocos2d for iPhone » Programming » Programming - Everything else

Fast set/getPixel for an opengl texture?

(79 posts) (23 voices)
  • Started 3 years ago by Lam
  • Latest reply from ayt

Tags:

  • CCMutableTexture2D
  • direct pixel access
  • getPixel
  • glDrawPixels
  • glReadPixels
  • glSubTexImage2D
  • glTexImage2D
  • glTexSubImage
  • glTexSubImage2D
  • mutable
  • OpenGL
  • openGLES
  • PBO
  • pixel
  • setPixel
  • texture
  • Texture2D
123Next »
  1. Lam
    Moderator

    I've been trying find a way to access pixels in a texture in order to get/set the color of a pixel.

    I've been fumbling around quite a lot. Hopefully someone can present an idea to accomplish this. My implementation works but is really slow.

    What I've done is extend Texture2D as a MutableTexture2D that allow you to access a pixel in a really slow manner.

    Since texture data resides in the GPU I had to store the array of pixel values.

    I've gone through these attempts at creating this class.
    1st:
    Using glTexImage2D to create a new texture from the data array. Which means I'm releasing the old texture, then binding a new texture. It works though really slowly.

    2nd:
    glTexSubImage2D allows portions of a texture image replacement so this should be faster but implementing this made MutableTexture2D slower than before... =/

    3rd:
    Read about PBO 'Pixel Buffer Objects' and the idea seems to fit my needs but, I think, it's not implemented in opengles1.1.

    Right now I'm at a loss of ideas. If anyone can suggest anything then that would be awesome.

    Posted 3 years ago #
  2. UseCase
    Member

    glReadPixels
    glDrawPixels

    these functions will be key to what you are trying to do.

    Posted 3 years ago #
  3. Lam
    Moderator

    I'll take a look at those calls. They seem to work on framebuffer data but I saw somewhere that you can bind a texture to a FBO (RenderTexture?). Maybe those calls will then work for me.

    I have to see if they're slow are not. Right now setting pixels is slow but reading them is fast since it's all stored in a matrix. Kind of like implementing per pixel collision.

    I guess what i'll really use is glDrawPixels.

    Thanks UseCase! I'll see what comes of this attempt.

    Posted 3 years ago #
  4. Lam
    Moderator

    Unfortunately glDrawPixels isn't implemented. =/
    But maybe i can draw GLpoints and use the RenderTexture class.

    Posted 3 years ago #
  5. jptsetung
    Member

    Maybe this is astupid suggestion, but is there a possibility drawing a 1x1 sprite of the wanted color on the RenderTexture, will behave like a drawPixel method ?

    Posted 3 years ago #
  6. Lam
    Moderator

    That's a good idea since i was thinking that. What i'm doing is drawing a GL_POINT using drawPoint(), which is a 1x1 sprite.

    Right now it doesn't work but it may be due the way I'm handling FBO's in the MutableTexture. I can't easily use RenderTexture =/ because the class I'm creating doesn't inherit CocosNode so I'm forced to try to recreate an implementation like RenderTexture but nothing is showing (my fault probably)...

    Posted 3 years ago #
  7. jptsetung
    Member

    Here are good readings about offscreen buffers in case you didn't read it already : http://developer.apple.com/iphone/library/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html#//apple_ref/doc/uid/TP40008793-CH103-SW1

    Posted 3 years ago #
  8. Lam
    Moderator

    Thanks for the info. I think The FBO works but it's probably my glOrtho call. =/
    In RenderTexture i see:

    glOrthof((float)-1.0 / widthRatio,  (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1,1);

    But that doesn't quite make sense, shouldn't glOrthof be called in glMatrixMode(GL_PROJECTION)?
    I have to figure out how this would apply to my class which doesn't extend CocosNode
    I'm thinking for just Texture2D:

    glOrthof(0, _size.width, 0, _size.height, -1, 1);
    Posted 3 years ago #
  9. Lam
    Moderator

    I got set/get pixel to work on textures that's relatively fast. If anyone ever wants to see the code and can help me optimize it then ask.

    I'll have to figure out how to post it here somehow.

    Posted 3 years ago #
  10. Steve Oldmeadow
    Administrator

    What do you call relatively fast? For example how quickly can you update and draw a screen full of pixels?

    Posted 3 years ago #
  11. jptsetung
    Member

    Yes Lam I'm interested in your code. Games that have pixel base collisions are usually very fun (lemmings, iShoot) so that would be definitely a great addition to cocos2D.

    Posted 3 years ago #
  12. Lam
    Moderator

    Well I created a game where i used the cocos2D ParticleSystem to generate the Particle example effects and wrote a collision routine for when particles collide with the image it gets added to the texture.

    I haven't used any metrics to determine exactly how fast it truly is but I don't believe any of my class ideas are needed to be added into the cocos2D framework or that anyone would really think it's necessary for cocos2d.

    With that in mind, I only bothered to check on the device and relatively fast is in relation to the old code that was really slow =/

    Posted 3 years ago #
  13. Lam
    Moderator

    Use this as freely as you wish, I don't really need you to credit me or anything but if you can try it out and improve upon the code. I'd love to know about it.

    Oh and to use any setPixel, fill, copy. Remember to call apply to actually update the texture.

    I would also love to figure out how to retain the texture data for a PVR image if anyone can point me in the right direction...

    MutableTexture2D.h

    ///
    //	MutableTexture2D extends cocos2D Texture2D.h
    //	Created by Lam Hoang Pham.
    //
    //	Allows for modifications of the texture data. If you load PVR's then you are
    //	out of luck. Supported pixelFormats will allow access to bitmap data.
    //
    //	Support for RGBA_4_4_4_4 and RGBA_5_5_5_1 was copied from:
    //	https://devforums.apple.com/message/37855#37855 by a1studmuffin
    //
    ///
    
    #import <UIKit/UIKit.h>
    #import <OpenGLES/ES1/gl.h>
    #import "Texture2D.h"
    #import "cocos2d.h"
    
    @interface MutableTexture2D : Texture2D
    {
    	void	*data_;
    	bool	dirty_;
    }
    //	Returns the maximum allowed texture size
    + (int) maxTextureSize;
    @end
    
    @interface MutableTexture2D(Image)
    ///
    //	Create a texture with an image
    ///
    - (id) initWithImage:(UIImage*) image;
    @end
    
    @interface MutableTexture2D (MutableTexture)
    ///
    //	Create a blank texture with canvas size and default pixel format
    ///
    + (id) textureWithSize:(CGSize) size;
    
    ///
    //	Create a blank texture with canvas size and chosen pixel format
    ///
    + (id) textureWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat;
    
    ///
    //	Create a blank texture with canvas size and default pixel format
    ///
    - (id) initWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat;
    
    ///
    //	@param pt is a point to get a pixel (0,0) is top-left to (width,height) bottom-right
    //	@returns a ccColor4B which is a colour, otherwise it returns Texture2DPixelClear
    ///
    - (ccColor4B) pixelAt:(CGPoint) pt;
    
    ///
    //	@param pt is a point to get a pixel (0,0) is top-left to (width,height) bottom-right
    //	@param c is a ccColor4B which is a colour.
    //	@returns if a pixel was set
    //	Remember to call apply to actually update the texture canvas.
    ///
    - (BOOL) setPixelAt:(CGPoint) pt rgba:(ccColor4B) c;
    
    ///
    //	Fill with specified colour
    ///
    - (void) fill:(ccColor4B) c;
    
    ///
    //	@param textureToCopy is the texture image to copy over
    //	@param offset also offset's the texture image
    ///
    - (void) copy:(MutableTexture2D*) textureToCopy offset:(CGPoint) offset;
    
    ///
    //	apply actually updates the texture with any new data we added.
    ///
    - (void) apply;
    @end
    
    ///
    //	Fast find for powers of 2
    ///
    extern bool IsPow2(uint v);
    
    ///
    //	Fast round to nearest power of 2 for 32-bit int's.
    ///
    extern uint RoundToNearestPow2(uint v);

    MutableTexture2D.m

    #import <OpenGLES/ES1/glext.h>
    #import "MutableTexture2D.h"
    
    ///
    //	Fast find for powers of 2
    ///
    bool IsPow2(uint v){
    	return (v > 1) && ((v & (v - 1)) == 0);
    }
    
    ///
    //	Fast round to nearest power of 2 for 32-bit int's.
    ///
    uint RoundToNearestPow2(uint v){
    	//if(v < 32) return 32;
    	if(v <= 1)return 2;
    	v--;
    	v |= v >> 1;
    	v |= v >> 2;
    	v |= v >> 4;
    	v |= v >> 8;
    	v |= v >> 16;
    	v++;
    	return v;
    }
    
    @implementation MutableTexture2D
    + (int) maxTextureSize {
    	return 1024;
    }
    - (id) initWithData:(const void*)data pixelFormat:(Texture2DPixelFormat)pixelFormat pixelsWide:(NSUInteger)width pixelsHigh:(NSUInteger)height contentSize:(CGSize)size
    {
    	if((self = [super initWithData:data pixelFormat:pixelFormat pixelsWide:width pixelsHigh:height contentSize:size])) {
    		data_ = NULL;
    		dirty_ = false;
    	}
    	return self;
    }
    
    - (void) dealloc
    {
    	if(data_){
    		free(data_);
    		data_ = NULL;
    	}
    
    	[super dealloc];
    }
    
    @end
    
    @implementation MutableTexture2D(Image)
    - (id) initWithImage:(UIImage *)uiImage
    {
    	NSUInteger				width,
    	height,
    	i;
    	CGContextRef			context = nil;
    	void*					data = nil;;
    	CGColorSpaceRef			colorSpace;
    	void*					tempData;
    	unsigned int*			inPixel32;
    	unsigned short*			outPixel16;
    	BOOL					hasAlpha;
    	CGImageAlphaInfo		info;
    	CGAffineTransform		transform;
    	CGSize					imageSize;
    	Texture2DPixelFormat    pixelFormat;
    	CGImageRef				image;
    	BOOL					sizeToFit = NO;
    
    	image = [uiImage CGImage];
    
    	if(image == NULL) {
    		[self release];
    		NSLog(@"Image is Null");
    		return nil;
    	}
    
    	info = CGImageGetAlphaInfo(image);
    	hasAlpha = ((info == kCGImageAlphaPremultipliedLast) || (info == kCGImageAlphaPremultipliedFirst) || (info == kCGImageAlphaLast) || (info == kCGImageAlphaFirst) ? YES : NO);
    
    	size_t bpp = CGImageGetBitsPerComponent(image);
    	if(CGImageGetColorSpace(image)) {
    		if(hasAlpha || bpp >= 8)
    			pixelFormat = [[self class] defaultAlphaPixelFormat];
    		else
    			pixelFormat = kTexture2DPixelFormat_RGB565;
    	} else  //NOTE: No colorspace means a mask image
    		pixelFormat = kTexture2DPixelFormat_A8;
    
    	imageSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image));
    	transform = CGAffineTransformIdentity;
    
    	width = imageSize.width;
    
    	if((width != 1) && (width & (width - 1))) {
    		i = 2;
    		while((sizeToFit ? 2 * i : i) < width)
    			i *= 2;
    		width = i;
    	}
    	height = imageSize.height;
    	if((height != 1) && (height & (height - 1))) {
    		i = 2;
    		while((sizeToFit ? 2 * i : i) < height)
    			i *= 2;
    		height = i;
    	}
    
    	// iPhone 3GS supports 2048 textures, so the maxtexturesize should be a variable, not a hardcoded value
    	NSAssert2( (width <= [[self class] maxTextureSize]) && (height <= [[self class] maxTextureSize]), @"Image is bigger than the supported %d x %d", [[self class] maxTextureSize], [[self class] maxTextureSize]);
    
    	//	while((width > kMaxTextureSize) || (height > kMaxTextureSize)) {
    	//		width /= 2;
    	//		height /= 2;
    	//		transform = CGAffineTransformScale(transform, 0.5f, 0.5f);
    	//		imageSize.width *= 0.5f;
    	//		imageSize.height *= 0.5f;
    	//	}
    
    	// Create the bitmap graphics context
    
    	switch(pixelFormat) {
    		case kTexture2DPixelFormat_RGBA8888:
    		case kTexture2DPixelFormat_RGBA4444:
    		case kTexture2DPixelFormat_RGB5A1:
    			colorSpace = CGColorSpaceCreateDeviceRGB();
    			data = malloc(height * width * 4);
    			context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    			CGColorSpaceRelease(colorSpace);
    			break;
    		case kTexture2DPixelFormat_RGB565:
    			colorSpace = CGColorSpaceCreateDeviceRGB();
    			data = malloc(height * width * 4);
    			context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big);
    			CGColorSpaceRelease(colorSpace);
    			break;
    		case kTexture2DPixelFormat_A8:
    			data = malloc(height * width);
    			context = CGBitmapContextCreate(data, width, height, 8, width, NULL, kCGImageAlphaOnly);
    			break;
    		default:
    			[NSException raise:NSInternalInconsistencyException format:@"Invalid pixel format"];
    	}
    
    	CGContextClearRect(context, CGRectMake(0, 0, width, height));
    	CGContextTranslateCTM(context, 0, height - imageSize.height);
    
    	if(!CGAffineTransformIsIdentity(transform))
    		CGContextConcatCTM(context, transform);
    	CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
    
    	// Repack the pixel data into the right format
    
    	if(pixelFormat == kTexture2DPixelFormat_RGB565) {
    		//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGGBBBBB"
    		tempData = malloc(height * width * 2);
    		inPixel32 = (unsigned int*)data;
    		outPixel16 = (unsigned short*)tempData;
    		for(i = 0; i < width * height; ++i, ++inPixel32)
    			*outPixel16++ = ((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | ((((*inPixel32 >> 8) & 0xFF) >> 2) << 5) | ((((*inPixel32 >> 16) & 0xFF) >> 3) << 0);
    		free(data);
    		data = tempData;
    
    	}
    	else if (pixelFormat == kTexture2DPixelFormat_RGBA4444) {
    		//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRGGGGBBBBAAAA"
    		tempData = malloc(height * width * 2);
    		inPixel32 = (unsigned int*)data;
    		outPixel16 = (unsigned short*)tempData;
    		for(i = 0; i < width * height; ++i, ++inPixel32)
    			*outPixel16++ =
    			((((*inPixel32 >> 0) & 0xFF) >> 4) << 12) | // R
    			((((*inPixel32 >> 8) & 0xFF) >> 4) << 8) | // G
    			((((*inPixel32 >> 16) & 0xFF) >> 4) << 4) | // B
    			((((*inPixel32 >> 24) & 0xFF) >> 4) << 0); // A
    
    		free(data);
    		data = tempData;
    
    	}
    	else if (pixelFormat == kTexture2DPixelFormat_RGB5A1) {
    		//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGBBBBBA"
    		tempData = malloc(height * width * 2);
    		inPixel32 = (unsigned int*)data;
    		outPixel16 = (unsigned short*)tempData;
    		for(i = 0; i < width * height; ++i, ++inPixel32)
    			*outPixel16++ =
    			((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | // R
    			((((*inPixel32 >> 8) & 0xFF) >> 3) << 6) | // G
    			((((*inPixel32 >> 16) & 0xFF) >> 3) << 1) | // B
    			((((*inPixel32 >> 24) & 0xFF) >> 7) << 0); // A
    
    		free(data);
    		data = tempData;
    	}
    	self = [self initWithData:data pixelFormat:pixelFormat pixelsWide:width pixelsHigh:height contentSize:imageSize];
    
    	// should be after calling super init
    	_hasPremultipliedAlpha = (info == kCGImageAlphaPremultipliedLast || info == kCGImageAlphaPremultipliedFirst);
    
    	CGContextRelease(context);
    
    	//	This is the only change =/ but we want to keep the data for mutable methods
    	data_ = data;
    
    	return self;
    }
    @end
    
    @implementation MutableTexture2D (MutableTexture)
    + (id) textureWithSize:(CGSize) size {
    	return [[[self alloc] initWithSize:size pixelFormat:[[self class] defaultAlphaPixelFormat]] autorelease];
    }
    + (id) textureWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat {
    	return [[[self alloc] initWithSize:size pixelFormat:pixelFormat] autorelease];
    }
    - (id) initWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat {
    	if((self = [super init])){
    		_format = pixelFormat;
    		_size = size;
    
    		_width = size.width;
    		if(!IsPow2(_width))
    			_width = RoundToNearestPow2(_width);
    
    		_height = size.height;
    		if(!IsPow2(_height))
    			_height = RoundToNearestPow2(_height);
    
    		int dataSize = 0;
    		switch (_format) {
    			case kTexture2DPixelFormat_RGBA8888:
    				dataSize = _width * _height * sizeof(int);
    				break;
    			case kTexture2DPixelFormat_RGBA4444:
    			case kTexture2DPixelFormat_RGB5A1:
    			case kTexture2DPixelFormat_RGB565:
    				dataSize = _width * _height * sizeof(short);
    				break;
    			case kTexture2DPixelFormat_A8:
    				dataSize = _width * _height;
    				break;
    			default:
    				break;
    		}
    
    		_maxS = _size.width / (float)_width;
    		_maxT = _size.height / (float)_height;
    
    		_hasPremultipliedAlpha = NO;
    		data_ = calloc(dataSize, 1);
    		NSAssert(data_, @"Low Memory, could not allocate Texture Data");
    
    		glGenTextures(1, &_name);
    		glBindTexture(GL_TEXTURE_2D, _name);
    
    		[self setAntiAliasTexParameters];
    
    		switch(_format)
    		{
    			case kTexture2DPixelFormat_RGBA8888:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data_);
    				break;
    			case kTexture2DPixelFormat_RGBA4444:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data_);
    				break;
    			case kTexture2DPixelFormat_RGB5A1:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, data_);
    				break;
    			case kTexture2DPixelFormat_RGB565:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _width, _height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data_);
    				break;
    			case kTexture2DPixelFormat_A8:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, _width, _height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data_);
    				break;
    			default:
    				[NSException raise:NSInternalInconsistencyException format:@""];
    
    		}
    	}
    	return self;
    }
    
    - (ccColor4B) pixelAt:(CGPoint) pt {
    	ccColor4B c = {0, 0, 0, 0};
    	if(!data_) return c;
    	if(pt.x < 0 || pt.y < 0) return c;
    	if(pt.x >= _size.width || pt.y >= _size.height) return c;
    
    	uint x = pt.x, y = pt.y;
    
    	if(_format == kTexture2DPixelFormat_RGBA8888){
    		uint *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		c.r = *pixel & 0xff;
    		c.g = (*pixel >> 8) & 0xff;
    		c.b = (*pixel >> 16) & 0xff;
    		c.a = (*pixel >> 24) & 0xff;
    	} else if(_format == kTexture2DPixelFormat_RGBA4444){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		c.r = *pixel & 0xf;
    		c.g = (*pixel >> 4) & 0xf;
    		c.b = (*pixel >> 8) & 0xf;
    		c.a = (*pixel >> 12) & 0xf;
    	} else if(_format == kTexture2DPixelFormat_RGB5A1){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		c.r = *pixel & 0x1f;
    		c.g = (*pixel >> 5) & 0x1f;
    		c.b = (*pixel >> 10) & 0x1f;
    		c.a = (*pixel >> 15) & 0x1;
    	} else if(_format == kTexture2DPixelFormat_RGB565){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		c.r = *pixel & 0x1f;
    		c.g = (*pixel >> 5) & 0x3f;
    		c.b = (*pixel >> 11) & 0x1f;
    		c.a = 255;
    	} else if(_format == kTexture2DPixelFormat_A8){
    		GLubyte *pixel = data_;
    		c.a = pixel[(y * _width) + x];
    	}
    
    	return c;
    }
    
    - (BOOL) setPixelAt:(CGPoint) pt rgba:(ccColor4B) c {
    	if(!data_)return NO;
    	if(pt.x < 0 || pt.y < 0) return NO;
    	if(pt.x >= _size.width || pt.y >= _size.height) return NO;
    	uint x = pt.x, y = pt.y;
    
    	dirty_ = true;
    
    	//	Shifted bit placement based on little-endian, let's make this more
    	//	portable =/
    
    	if(_format == kTexture2DPixelFormat_RGBA8888){
    		uint *pixel = data_;
    		pixel[(y * _width) + x] = (c.a << 24) | (c.b << 16) | (c.g << 8) | c.r;
    	} else if(_format == kTexture2DPixelFormat_RGBA4444){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		*pixel = ((c.a >> 4) << 12) | ((c.b >> 4) << 8) | ((c.g >> 4) << 4) | (c.r >> 4);
    	} else if(_format == kTexture2DPixelFormat_RGB5A1){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		*pixel = (c.r >> 3) | ((c.g >> 3) << 5) | ((c.b >> 3) << 10) | ((c.a >> 7) << 15);
    	} else if(_format == kTexture2DPixelFormat_RGB565){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		*pixel = ((c.b >> 3) << 11) | ((c.g >> 2) << 5) | (c.r >> 3);
    	} else if(_format == kTexture2DPixelFormat_A8){
    		GLubyte *pixel = data_;
    		pixel[(y * _width) + x] = c.a;
    	} else {
    		dirty_ = false;
    		return NO;
    	}
    	return YES;
    }
    
    - (void) fill:(ccColor4B) p {
    	for(int r = 0; r < _size.height;++r){
    		for(int c = 0; c < _size.width; ++c){
    			[self setPixelAt:CGPointMake(c, r) rgba:p];
    		}
    	}
    }
    
    - (void) copy:(MutableTexture2D*) textureToCopy offset:(CGPoint) offset {
    	for(int r = 0; r < _size.height;++r){
    		for(int c = 0; c < _size.width; ++c){
    			[self setPixelAt:CGPointMake(c + offset.x, r + offset.y) rgba:[textureToCopy pixelAt:CGPointMake(c, r)]];
    		}
    	}
    }
    
    - (void) apply {
    	if(!dirty_) return;
    	if(!data_) return;
    
    	glBindTexture(GL_TEXTURE_2D, _name);
    
    	switch(_format)
    	{
    		case kTexture2DPixelFormat_RGBA8888:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data_);
    			break;
    		case kTexture2DPixelFormat_RGBA4444:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data_);
    			break;
    		case kTexture2DPixelFormat_RGB5A1:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, data_);
    			break;
    		case kTexture2DPixelFormat_RGB565:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _width, _height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data_);
    			break;
    		case kTexture2DPixelFormat_A8:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, _width, _height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data_);
    			break;
    		default:
    			[NSException raise:NSInternalInconsistencyException format:@""];
    	}
    	dirty_ = false;
    }
    @end
    Posted 3 years ago #
  14. Codemattic
    Moderator

    So if you want to use this with Sprites do you create a MutableSprite thats a subclass of MutableTexture2D - duplicating all of the Sprite functionality? Or just replace Texture2D with MutableTexture2D? I wonder if future version of cocos2d could combine the two - what can Texture2d do that MT2D cant? Just pvr? I dont think you are going to be able to modify pvr's on the fly, wo first rendering them and losing the benefits of the compression.

    Thanks for the code!

    Posted 3 years ago #
  15. Lam
    Moderator

    @Codemattic
    Yeah, Since MutableTexture2D extends Texture2D you can pass a MutableTexture2D instance to a sprite.

    That's what I'm doing right now. I have a Sprite that gets a MutableTexture2D instead of just Texture2D and then I can set and get pixels from it for collision detection.

    Posted 3 years ago #
  16. Lam
    Moderator

    Improved MutableTexture2D.m

    #import <OpenGLES/ES1/glext.h>
    #import "MutableTexture2D.h"
    
    ///
    //	Fast find for powers of 2
    ///
    bool IsPow2(uint v){
    	return (v > 1) && ((v & (v - 1)) == 0);
    }
    
    ///
    //	Fast round to nearest power of 2 for 32-bit int's.
    ///
    uint RoundToNearestPow2(uint v){
    	//if(v < 32) return 32;
    	if(v <= 1)return 2;
    	v--;
    	v |= v >> 1;
    	v |= v >> 2;
    	v |= v >> 4;
    	v |= v >> 8;
    	v |= v >> 16;
    	v++;
    	return v;
    }
    
    @implementation MutableTexture2D
    + (int) maxTextureSize {
    	return 1024;
    }
    - (id) initWithData:(const void*)data pixelFormat:(Texture2DPixelFormat)pixelFormat pixelsWide:(NSUInteger)width pixelsHigh:(NSUInteger)height contentSize:(CGSize)size
    {
    	if((self = [super initWithData:data pixelFormat:pixelFormat pixelsWide:width pixelsHigh:height contentSize:size])) {
    		data_ = NULL;
    		dirty_ = false;
    	}
    	return self;
    }
    
    - (void) dealloc
    {
    	if(data_){
    		free(data_);
    		data_ = NULL;
    	}
    
    	[super dealloc];
    }
    
    @end
    
    @implementation MutableTexture2D(Image)
    - (id) initWithImage:(UIImage *)uiImage
    {
    	NSUInteger				width,
    	height,
    	i;
    	CGContextRef			context = nil;
    	void*					data = nil;;
    	CGColorSpaceRef			colorSpace;
    	void*					tempData;
    	unsigned int*			inPixel32;
    	unsigned short*			outPixel16;
    	BOOL					hasAlpha;
    	CGImageAlphaInfo		info;
    	CGAffineTransform		transform;
    	CGSize					imageSize;
    	Texture2DPixelFormat    pixelFormat;
    	CGImageRef				image;
    	BOOL					sizeToFit = NO;
    
    	image = [uiImage CGImage];
    
    	if(image == NULL) {
    		[self release];
    		NSLog(@"Image is Null");
    		return nil;
    	}
    
    	info = CGImageGetAlphaInfo(image);
    	hasAlpha = ((info == kCGImageAlphaPremultipliedLast) || (info == kCGImageAlphaPremultipliedFirst) || (info == kCGImageAlphaLast) || (info == kCGImageAlphaFirst) ? YES : NO);
    
    	size_t bpp = CGImageGetBitsPerComponent(image);
    	if(CGImageGetColorSpace(image)) {
    		if(hasAlpha || bpp >= 8)
    			pixelFormat = [[self class] defaultAlphaPixelFormat];
    		else
    			pixelFormat = kTexture2DPixelFormat_RGB565;
    	} else  //NOTE: No colorspace means a mask image
    		pixelFormat = kTexture2DPixelFormat_A8;
    
    	imageSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image));
    	transform = CGAffineTransformIdentity;
    
    	width = imageSize.width;
    
    	if((width != 1) && (width & (width - 1))) {
    		i = 2;
    		while((sizeToFit ? 2 * i : i) < width)
    			i *= 2;
    		width = i;
    	}
    	height = imageSize.height;
    	if((height != 1) && (height & (height - 1))) {
    		i = 2;
    		while((sizeToFit ? 2 * i : i) < height)
    			i *= 2;
    		height = i;
    	}
    
    	// iPhone 3GS supports 2048 textures, so the maxtexturesize should be a variable, not a hardcoded value
    	NSAssert2( (width <= [[self class] maxTextureSize]) && (height <= [[self class] maxTextureSize]), @"Image is bigger than the supported %d x %d", [[self class] maxTextureSize], [[self class] maxTextureSize]);
    
    	//	while((width > kMaxTextureSize) || (height > kMaxTextureSize)) {
    	//		width /= 2;
    	//		height /= 2;
    	//		transform = CGAffineTransformScale(transform, 0.5f, 0.5f);
    	//		imageSize.width *= 0.5f;
    	//		imageSize.height *= 0.5f;
    	//	}
    
    	// Create the bitmap graphics context
    
    	switch(pixelFormat) {
    		case kTexture2DPixelFormat_RGBA8888:
    		case kTexture2DPixelFormat_RGBA4444:
    		case kTexture2DPixelFormat_RGB5A1:
    			colorSpace = CGColorSpaceCreateDeviceRGB();
    			data = malloc(height * width * 4);
    			context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    			CGColorSpaceRelease(colorSpace);
    			break;
    		case kTexture2DPixelFormat_RGB565:
    			colorSpace = CGColorSpaceCreateDeviceRGB();
    			data = malloc(height * width * 4);
    			context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big);
    			CGColorSpaceRelease(colorSpace);
    			break;
    		case kTexture2DPixelFormat_A8:
    			data = malloc(height * width);
    			context = CGBitmapContextCreate(data, width, height, 8, width, NULL, kCGImageAlphaOnly);
    			break;
    		default:
    			[NSException raise:NSInternalInconsistencyException format:@"Invalid pixel format"];
    	}
    
    	CGContextClearRect(context, CGRectMake(0, 0, width, height));
    	CGContextTranslateCTM(context, 0, height - imageSize.height);
    
    	if(!CGAffineTransformIsIdentity(transform))
    		CGContextConcatCTM(context, transform);
    	CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
    
    	// Repack the pixel data into the right format
    
    	if(pixelFormat == kTexture2DPixelFormat_RGB565) {
    		//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGGBBBBB"
    		tempData = malloc(height * width * 2);
    		inPixel32 = (unsigned int*)data;
    		outPixel16 = (unsigned short*)tempData;
    		for(i = 0; i < width * height; ++i, ++inPixel32)
    			*outPixel16++ = ((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | ((((*inPixel32 >> 8) & 0xFF) >> 2) << 5) | ((((*inPixel32 >> 16) & 0xFF) >> 3) << 0);
    		free(data);
    		data = tempData;
    
    	}
    	else if (pixelFormat == kTexture2DPixelFormat_RGBA4444) {
    		//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRGGGGBBBBAAAA"
    		tempData = malloc(height * width * 2);
    		inPixel32 = (unsigned int*)data;
    		outPixel16 = (unsigned short*)tempData;
    		for(i = 0; i < width * height; ++i, ++inPixel32)
    			*outPixel16++ =
    			((((*inPixel32 >> 0) & 0xFF) >> 4) << 12) | // R
    			((((*inPixel32 >> 8) & 0xFF) >> 4) << 8) | // G
    			((((*inPixel32 >> 16) & 0xFF) >> 4) << 4) | // B
    			((((*inPixel32 >> 24) & 0xFF) >> 4) << 0); // A
    
    		free(data);
    		data = tempData;
    
    	}
    	else if (pixelFormat == kTexture2DPixelFormat_RGB5A1) {
    		//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGBBBBBA"
    		tempData = malloc(height * width * 2);
    		inPixel32 = (unsigned int*)data;
    		outPixel16 = (unsigned short*)tempData;
    		for(i = 0; i < width * height; ++i, ++inPixel32)
    			*outPixel16++ =
    			((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | // R
    			((((*inPixel32 >> 8) & 0xFF) >> 3) << 6) | // G
    			((((*inPixel32 >> 16) & 0xFF) >> 3) << 1) | // B
    			((((*inPixel32 >> 24) & 0xFF) >> 7) << 0); // A
    
    		free(data);
    		data = tempData;
    	}
    	self = [self initWithData:data pixelFormat:pixelFormat pixelsWide:width pixelsHigh:height contentSize:imageSize];
    
    	// should be after calling super init
    	_hasPremultipliedAlpha = (info == kCGImageAlphaPremultipliedLast || info == kCGImageAlphaPremultipliedFirst);
    
    	CGContextRelease(context);
    
    	//	This is the only change =/ but we want to keep the data for mutable methods
    	data_ = data;
    
    	return self;
    }
    @end
    
    @implementation MutableTexture2D (MutableTexture)
    + (id) textureWithSize:(CGSize) size {
    	return [[[self alloc] initWithSize:size pixelFormat:[[self class] defaultAlphaPixelFormat]] autorelease];
    }
    + (id) textureWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat {
    	return [[[self alloc] initWithSize:size pixelFormat:pixelFormat] autorelease];
    }
    - (id) initWithSize:(CGSize) size pixelFormat:(Texture2DPixelFormat) pixelFormat {
    	if((self = [super init])){
    		_format = pixelFormat;
    		_size = size;
    
    		_width = size.width;
    		if(!IsPow2(_width))
    			_width = RoundToNearestPow2(_width);
    
    		_height = size.height;
    		if(!IsPow2(_height))
    			_height = RoundToNearestPow2(_height);
    
    		int dataSize = 0;
    		switch (_format) {
    			case kTexture2DPixelFormat_RGBA8888:
    				dataSize = _width * _height * sizeof(int);
    				break;
    			case kTexture2DPixelFormat_RGBA4444:
    			case kTexture2DPixelFormat_RGB5A1:
    			case kTexture2DPixelFormat_RGB565:
    				dataSize = _width * _height * sizeof(short);
    				break;
    			case kTexture2DPixelFormat_A8:
    				dataSize = _width * _height;
    				break;
    			default:
    				break;
    		}
    
    		_maxS = _size.width / (float)_width;
    		_maxT = _size.height / (float)_height;
    
    		_hasPremultipliedAlpha = NO;
    		data_ = calloc(dataSize, 1);
    		NSAssert(data_, @"Low Memory, could not allocate Texture Data");
    
    		glGenTextures(1, &_name);
    		glBindTexture(GL_TEXTURE_2D, _name);
    
    		[self setAntiAliasTexParameters];
    
    		switch(_format)
    		{
    			case kTexture2DPixelFormat_RGBA8888:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data_);
    				break;
    			case kTexture2DPixelFormat_RGBA4444:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data_);
    				break;
    			case kTexture2DPixelFormat_RGB5A1:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, data_);
    				break;
    			case kTexture2DPixelFormat_RGB565:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _width, _height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data_);
    				break;
    			case kTexture2DPixelFormat_A8:
    				glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, _width, _height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data_);
    				break;
    			default:
    				[NSException raise:NSInternalInconsistencyException format:@""];
    
    		}
    	}
    	return self;
    }
    
    - (ccColor4B) pixelAt:(CGPoint) pt {
    	ccColor4B c = {0, 0, 0, 0};
    	if(!data_) return c;
    	if(pt.x < 0 || pt.y < 0) return c;
    	if(pt.x >= _size.width || pt.y >= _size.height) return c;
    
    	uint x = pt.x, y = pt.y;
    
    	if(_format == kTexture2DPixelFormat_RGBA8888){
    		uint *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		c.r = *pixel & 0xff;
    		c.g = (*pixel >> 8) & 0xff;
    		c.b = (*pixel >> 16) & 0xff;
    		c.a = (*pixel >> 24) & 0xff;
    	} else if(_format == kTexture2DPixelFormat_RGBA4444){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		c.a = ((*pixel & 0xf) << 4) | (*pixel & 0xf);
    		c.b = (((*pixel >> 4) & 0xf) << 4) | ((*pixel >> 4) & 0xf);
    		c.g = (((*pixel >> 8) & 0xf) << 4) | ((*pixel >> 8) & 0xf);
    		c.r = (((*pixel >> 12) & 0xf) << 4) | ((*pixel >> 12) & 0xf);
    	} else if(_format == kTexture2DPixelFormat_RGB5A1){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		c.r = ((*pixel >> 11) & 0x1f)<<3;
    		c.g = ((*pixel >> 6) & 0x1f)<<3;
    		c.b = ((*pixel >> 1) & 0x1f)<<3;
    		c.a = (*pixel & 0x1)*255;
    	} else if(_format == kTexture2DPixelFormat_RGB565){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		c.b = (*pixel & 0x1f)<<3;
    		c.g = ((*pixel >> 5) & 0x3f)<<2;
    		c.r = ((*pixel >> 11) & 0x1f)<<3;
    		c.a = 255;
    	} else if(_format == kTexture2DPixelFormat_A8){
    		GLubyte *pixel = data_;
    		c.a = pixel[(y * _width) + x];
    		// Default white
    		c.r = 255;
    		c.g = 255;
    		c.b = 255;
    	}
    
    	return c;
    }
    
    - (BOOL) setPixelAt:(CGPoint) pt rgba:(ccColor4B) c {
    	if(!data_)return NO;
    	if(pt.x < 0 || pt.y < 0) return NO;
    	if(pt.x >= _size.width || pt.y >= _size.height) return NO;
    	uint x = pt.x, y = pt.y;
    
    	dirty_ = true;
    
    	//	Shifted bit placement based on little-endian, let's make this more
    	//	portable =/
    
    	if(_format == kTexture2DPixelFormat_RGBA8888){
    		uint *pixel = data_;
    		pixel[(y * _width) + x] = (c.a << 24) | (c.b << 16) | (c.g << 8) | c.r;
    	} else if(_format == kTexture2DPixelFormat_RGBA4444){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		*pixel = ((c.r >> 4) << 12) | ((c.g >> 4) << 8) | ((c.b >> 4) << 4) | (c.a >> 4);
    	} else if(_format == kTexture2DPixelFormat_RGB5A1){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		*pixel = ((c.r >> 3) << 11) | ((c.g >> 3) << 6) | ((c.b >> 3) << 1) | (c.a > 0);
    	} else if(_format == kTexture2DPixelFormat_RGB565){
    		GLushort *pixel = data_;
    		pixel = pixel + (y * _width) + x;
    		*pixel = ((c.r >> 3) << 11) | ((c.g >> 2) << 5) | (c.b >> 3);
    	} else if(_format == kTexture2DPixelFormat_A8){
    		GLubyte *pixel = data_;
    		pixel[(y * _width) + x] = c.a;
    	} else {
    		dirty_ = false;
    		return NO;
    	}
    	return YES;
    }
    
    - (void) fill:(ccColor4B) p {
    	for(int r = 0; r < _size.height;++r){
    		for(int c = 0; c < _size.width; ++c){
    			[self setPixelAt:CGPointMake(c, r) rgba:p];
    		}
    	}
    }
    
    - (void) copy:(MutableTexture2D*) textureToCopy offset:(CGPoint) offset {
    	for(int r = 0; r < _size.height;++r){
    		for(int c = 0; c < _size.width; ++c){
    			[self setPixelAt:CGPointMake(c + offset.x, r + offset.y) rgba:[textureToCopy pixelAt:CGPointMake(c, r)]];
    		}
    	}
    }
    
    - (void) apply {
    	if(!dirty_) return;
    	if(!data_) return;
    
    	glBindTexture(GL_TEXTURE_2D, _name);
    
    	switch(_format)
    	{
    		case kTexture2DPixelFormat_RGBA8888:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data_);
    			break;
    		case kTexture2DPixelFormat_RGBA4444:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data_);
    			break;
    		case kTexture2DPixelFormat_RGB5A1:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, data_);
    			break;
    		case kTexture2DPixelFormat_RGB565:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _width, _height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data_);
    			break;
    		case kTexture2DPixelFormat_A8:
    			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, _width, _height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data_);
    			break;
    		default:
    			[NSException raise:NSInternalInconsistencyException format:@""];
    	}
    	dirty_ = false;
    }
    @end
    Posted 3 years ago #
  17. jptsetung
    Member

    That's really awesome, thx for sharing. I will make some testings later.

    Posted 3 years ago #
  18. michaelm
    Member

    Hi Lam, I've been trying to get your mutabletexture example added and working but I can't get it to. I've tried in a .82 and a .9 project (with changes for the renaming) but I can't figure out why it won't work, (even to instanciate one without errors). Is there any way you would be willing to post a working project?

    Posted 3 years ago #
  19. Lam
    Moderator

    Sure!
    There have been some improvements to the code during the month I moved it over to the project: Pixel pile

    It worked but still couldn't meet up to the demands of setting large amounts pixels constantly (especially on higher levels) so I got to work reducing the performance hit to get a good running frame rate.

    Updated Mutable Texture for cocos 0.99

    If you need anything like some documentation for using the class or a demo then just ask and I'll write up something.

    Posted 3 years ago #
  20. yarri
    Member

    Hi, for what it's worth Walter Rawdanik posted his technique on the Oolong forum for using FBO's and a call to glTexSubImage to access pixels directly and update parts of a texture. He used this to create this type of realtime brush effect with pretty good FPS:
    http://www.warmi.net/tmp/scratchme.mov

    --yarri

    Posted 3 years ago #
  21. Lam
    Moderator

    @yarri
    Nice! Is there a link to that forum post? I'm google'ing 'Walter Rawdanik Oolong FBO glTexSubImage' but I seem to get nothing.

    This would have been awesome if I found it 3 months ago when I had to figure out an approach to this problem :D

    Anyway, I resorted to glTexImage for CCMutableTexture2D to update every texel in the texture. I can see the performance improvements if I switched over to a glTexSubImage routine.

    I guess to take advantage of modifying sub texels in an image area I would have to think of an implementation for finding optimal regions in the texture area to update based on the pixels a user has currently set.

    Posted 3 years ago #
  22. michaelm
    Member

    Thanks Lam, works like a charm.

    Posted 3 years ago #
  23. Steve Oldmeadow
    Administrator

    @Lam - Oolong forum is actually just a mailing list. http://lists.oolongengine.com/listinfo.cgi/oolong-oolongengine.com
    It has some interesting stuff and the traffic is pretty low, you need to be a subscriber to view the archives.

    However, the approach they were talking about is not really suitable for updating whole textures. Basically they are just drawing a 2d array of coloured "quads" into a texture using the FBO render to texture technique. It could be useful for a pixel pile type algorithm because you are only updating a few pixels each frame. However, for an algorithm where the whole texture is changing every frame it is not suitable. You'll also take a performance hit for updating the FBO texture on pre 3rd gen devices which is going to give you sub 30 fps performance on each frame you do the update.

    Posted 3 years ago #
  24. Lam
    Moderator

    @Steve Oldmeadow
    I'll probably take a look at that mailing list.

    I see so it's pretty similar to CCRenderTexture.

    Posted 3 years ago #
  25. Steve Oldmeadow
    Administrator

    @Lam - yes, basically it is using the same technique as CCRenderTexture (bind texture to FBO). Then drawing coloured squares into the texture for the pixels. It seems there is some anomaly with using integer vertices that gives a speed boost. I've played around with different approaches such as using points, point sprites and coloured "quads" - all give similar performance but I didn't try different formats for defining the vertices I just always used floats.

    Posted 3 years ago #
  26. warmi
    Member

    In that demo http://www.warmi.net/tmp/scratchme.mov I actually didn't use glTexSubImage at all .. just pure rendering to FBOs with some tricks ( different texture formats and combiners) to get this to perform at around 45 fps.

    Posted 3 years ago #
  27. Steve Oldmeadow
    Administrator

    @warmi, that is interesting. Is 45 fps on a 1st gen device? Care to share the texture formats and combiners?

    Posted 3 years ago #
  28. warmi
    Member

    Yeah it is on a 1st gen device - the 3gs can run this kind of code with both hands (texture units ?) tied behind it’s back.

    As you know there is no secret magic bullet here and it is mostly just messing around to get things working for a particular scenario.
    In my case I needed it to be interactive and thus I couldn’t afford to run at anything less than 40 fps.

    It is basically an incremental rendering to the FBO at half the framebuffer size ( a 256x256 FBO with a 160x240 viewport) using GL_UNSIGNED_SHORT_4_4_4_4 texture format.
    When I say incremental I mean not clearing the FBO at every frame just incrementally rendering new quads as needed ( which is exactly what I needed to get the scratching effect).

    The animated background is just a single quad with a custom texture matrix used for animation.
    On top of that I render the RTT texture offset by a few pixels to get the drop shadow effect and as the very last thing I render the foreground image ( Scratch Me) using multitexturing ( the image itself and
    and the RTT texture again , this time used to mask out the image using texture combiners)

    Originally I was rendering the whole thing with a full-sized FBO (480 x320) but I coudn’t get it to perform fast enough so I had to settle with a 240x160 rtt which is just as well because for this particular effect
    it actually looked better that way.

    Posted 3 years ago #
  29. codeflakes
    Member

    Hi guys,

    Just discover this great piece of code from Lam and I'm trying to use it.
    My problem is that I try to create a mutable texture from a texture.

    CCMutableTexture2D* texture = [CCMutableTexture2D textureWithTexture2D:[tile texture]];

    like this but the data_ variable never gets initialized this way.
    It seems only the constructor with a UIImage works or is implemented.
    Am I missing something ? I only need to read image pixels not write them. Is this the best way to do it ?

    Thanks

    Posted 3 years ago #
  30. mungler
    Member

    This MutableTexture thingy is perfect for my game - kudos to Lam, great work!

    However, I can't get it to compile with Cocos 0.99.3 (haven't tried 0.99.4 yet...)

    Anyone smarter than me willing to clean up the zip file version Lam posted so it'll compile with the latest Cocos?

    EDIT: Ignore me, simply had to find a few instances of Texture2DPixelFormat and change those to CCTexture2DPixelFormat. Guess that class changed since Lam wrote this code. Great stuff, onwards and upwards. (Will leave this here in case anyone else has same issue..)

    Posted 2 years ago #

RSS feed for this topic

123Next »

Reply »

You must log in to post.

cocos2d for iPhone is proudly powered by bbPress.