OpenGL buffers usage - c

I am new to OpenGL and I am currently reading OpenGL Superbible. In the following code, it creates 3 buffer names, each one is bound to GL_TEXTURE_BUFFER_ARB, then some data is buffered.
My question is, when a new buffer is bound to GL_TEXTURE_BUFFER_ARB, what happens to the previous bufferde data? How is it used?
// Create 3 new buffer objects
glGenBuffers(3,texBO);
glGenTextures(1, &texBOTexture);
int count = 0;
float* fileData = 0;
// Load first texBO with a tangent-like curve, 1024 values
fileData = LoadFloatData(“LumTan.data”, &count);
if (count > 0)
{
glBindBuffer(GL_TEXTURE_BUFFER_ARB, texBO[0]);
glBufferData(GL_TEXTURE_BUFFER_ARB, sizeof(float)*count,
fileData, GL_STATIC_DRAW);
delete fileData;
}
// Load second texBO with a sine-like curve, 1024 values
fileData = LoadFloatData(“LumSin.data”, &count);
if (count > 0)
{
glBindBuffer(GL_TEXTURE_BUFFER_ARB, texBO[1]);
glBufferData(GL_TEXTURE_BUFFER_ARB, sizeof(float)*count,
fileData, GL_STATIC_DRAW);
delete fileData;
}
// Load third texBO with a linear curve, 1024 values
fileData = LoadFloatData(“LumLinear.data”, &count);
if (count > 0)
{
glBindBuffer(GL_TEXTURE_BUFFER_ARB, texBO[2]);
glBufferData(GL_TEXTURE_BUFFER_ARB, sizeof(float)*count,
fileData, GL_STATIC_DRAW);
delete fileData;
}
// Load the Tan ramp first
glBindBuffer(GL_TEXTURE_BUFFER_ARB, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER_ARB, texBOTexture);
glTexBuffer(GL_TEXTURE_BUFFER_ARB, GL_R32F, texBO[0]);
glActiveTexture(GL_TEXTURE0);
Actually I don't understand where the data in these buffers is actually used. To me, the code looks like:
int x = 3;
x = 7; //what about 3?
x = 5; //what about 7?
Moreover, after doing this (binding buffers, then buffering data into these buffers) to the three buffers, all the buffers are unbound from GL_TEXTURE_BUFFER_ARB by binding 0 to it.

All OpenGL object are represented by an unique id.
So if you create a Shader, Programm, Buffer, ... then you get an ID for these objects. These Objects live on the graphic card until you call delete them with glDelete[...] the commands you call for those objects are applied to the currently bound object glBind[...].
You can imagine it like a dialog between you and the gpu:
You tell the GPU with which object you currently want to work
The GPU activate this object for you
You then tell the GPU what you want to do with the the currently active object
The GPU applies this information to the active object.
With glGenBuffers(3,texBO) you create 3 Buffers are create and their ids are stored in texBO.
glBindBuffer(GL_TEXTURE_BUFFER_ARB, texBO[0]);:
Active the buffer with that is represented by the id stored in texBO[0]
glBufferData(GL_TEXTURE_BUFFER_ARB, sizeof(float)*count,
fileData, GL_STATIC_DRAW);:
Popolate the currently active Buffer (in this case texBO[0]) with the data stored in fileData.
glBindBuffer(GL_TEXTURE_BUFFER_ARB, texBO[1]);:
Now activates the buffer with that is represented by the id stored in texBO[1] the other buffer still exists but is not currently bound.

Related

supercollider - access buffer information inside a `Pbind` that uses a buffer array

in brief
i have an array of buffers; those are passed to a synth at random using a Pbind ; i need to access info on the current buffer from within the Pbind but I need help doing that !
explanation of the problem
i have loaded an array of buffers containing samples. those samples must be played in a random order (and at random intervals, but that's for later). to do so, i pass those buffers to a synth inside a Pbind. i want to set the \dur key to be the length of the current buffer being played. the thing is, that i can't find a way to access info on the current buffer from within the Pbind. i have tried using Pkey, Pfset and Plambda, but to no success.
does somebody know how to do this ???
code
the sounds are played using:
SynthDef(\player, {
/*
play a file from a buffer
out: the output channel
bufnum: the buffer to play
*/
arg out=0, bufnum;
Out.ar(
out,
PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum), doneAction: Done.freeSelf)) ! 2
)
}).add;
the buffers are loaded in an array:
path = PathName.new("/path/to/files");
bufferArray = Array.new(100);
path.filesDo({
arg file;
bufferArray.add( Buffer.read(s, file.fullPath) );
});
my Pbind pattern works like this:
i define a \buffer value which is a single buffer from the array
i pass this \buffer to my synth
i then try to calculate its duration (\dur) by dividing the number of frames of the buffer by its sample rate. this is what i can't seem to get right
p = Pbind(
\buffer, Prand(bufferArray, inf),
\instrument, \player,
\bufnum, Pkey(\buffer),
\dur, (Pkey(\buffer.numFrames) / Pkey(\buffer.sampleRate))
)
thanks in advance for your help !!
solution to the problem: how to access buffer information inside a Pbind pattern
after hours of searching, i've found a solution to this problem on the supercollider forum, and i'm posting my own solution in case others are looking on here, like i was !
define a global array of buffers
this isn't compulsory, but it allows to only create the buffer array once; the array is created asynchronously using the action parameter of Buffer.read(), which allows to trigger a function once the buffer is loaded:
var path;
Buffer.freeAll; // avoid using all buffers in server
path = PathName.new("/path/to/sound/files");
~bufferArray = Array.new(100);
path.filesDo({
// add the buffer to `~bufferArray` asynchronously
arg file;
b = Buffer.read(s, file.fullPath, action: {
arg buffer;
~bufferArray.add( buffer );
})
});
play the synth and use Pfunc to access buffer information inside of the Pbind
this is the solution per se:
define a Pbind pattern which activates a synth to play the buffer.
inside that, define a \buffer variable to hold the current buffer.
then, access data on that buffer inside of a Pfunc. this generates an argument containing the last event in the Pbind. using this event, the buffer data can be accessed
p = Pbind(
\buffer, Prand(~bufferArray, inf), // randomly access one buffer inside of the array
\instrument, \player,
\bufnum, Pfunc { arg event; event[\buffer] }, // define a `Pfunc` function to access the previous event containing a `\buffer` variable
\dur, Pfunc { arg event; event[\buffer].numFrames / event[\buffer].sampleRate } // duration
);
p.play;
see the original answer on the supercollider forum for more details !

SDL2 Texture sometimes empty after loading multiple 8 bit surfaces

I'll try to make that question as concise as possible, but don't hesitate to ask for clarification.
I'm dealing with legacy code, and I'm trying to load thousands of 8 bit images from the disk to create a texture for each.
I've tried multiple things, and I'm at the point where I'm trying to load my 8 bits images into a 32 bits surface, and then create a texture from that surface.
The problem : while loading and 8 bit image onto a 32 bit surface is working, when I try to SDL_CreateTextureFromSurface, I end up with a lot of textures that are completely blank (full of transparent pixels, 0x00000000).
Not all textures are wrong, thought. Each time I run the program, I get different "bad" textures. Sometimes there's more, sometimes there's less. And when I trace the program, I always end up with a correct texture (is that a timing problem?)
I know that the loading to the SDL_Surface is working, because I'm saving all the surfaces to the disk, and they're all correct. But I inspected the textures using NVidia NSight Graphics, and more than half of them are blank.
Here's the offending code :
int __cdecl IMG_SavePNG(SDL_Surface*, const char*);
SDL_Texture* Resource8bitToTexture32(SDL_Renderer* renderer, SDL_Color* palette, int paletteSize, void* dataAddress, int Width, int Height)
{
u32 uiCurrentOffset;
u32 uiSourceLinearSize = (Width * Height);
SDL_Color *currentColor;
char strSurfacePath[500];
// The texture we're creating
SDL_Texture* newTexture = NULL;
// Load image at specified address
SDL_Surface* tempSurface = SDL_CreateRGBSurface(0x00, Width, Height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_SetSurfaceBlendMode(tempSurface, SDL_BLENDMODE_NONE);
if(SDL_MUSTLOCK(tempSurface)
SDL_LockSurface(tempSurface);
for(uiCurrentOffset = 0; uiCurrentOffset < uiSourceLinearSize; uiCurrentOffset++)
{
currentColor = &palette[pSourceData[uiCurrentOffset]];
if(pSourceData[uiCurrentOffset] != PC_COLOR_TRANSPARENT)
{
((u32*)tempSurface->pixels)[uiCurrentOffset] = (u32)((currentColor->a << 24) + (currentColor->r << 16) + (currentColor->g << 8) + (currentColor->b << 0));
}
}
if(SDL_MUSTLOCK(tempSurface)
SDL_UnlockSurface(tempSurface);
// Create texture from surface pixels
newTexture = SDL_CreateTextureFromSurface(renderer, tempSurface);
// Save the surface to disk for verification only
sprintf(strSurfacePath, "c:\\tmp\\surfaces\\%s.png", GenerateUniqueName());
IMG_SavePNG(tempSurface, strSurfacePath);
// Get rid of old loaded surface
SDL_FreeSurface(tempSurface);
return newTexture;
}
Note that in the original code, I'm checking for boundaries, and for NULL after the SDL_Create*. I'm also aware that it would be better to have a spritesheet for the textures instead of loading each texture individually.
EDIT :
Here's a sample of what I'm observing in NSight if I capture a frame and use the Resources View.
The first 3186 textures are correct. Then I get 43 empty textures. Then I get 228 correct textures. Then 100 bad ones. Then 539 correct ones. Then 665 bad ones. It goes on randomly like that, and it changes each time I run my program.
Again, each time the surfaces saved by IMG_SavePNG are correct. This seems to indicate that something happens when I call SDL_CreateTextureFromSurface but at that point, I don't want to rule anything out, because it's a very weird problem, and it smells undefined behaviour all over the place. But I just can't find the problem.
With the help of #mark-benningfield, I was able to find the problem.
TL;DR
There's a bug (or at least, an undocumented feature) in SDL with the DX11 renderer. There's a work-around ; see at the end.
CONTEXT
I'm trying to load around 12,000 textures when my program start. I know it's not a good idea, but I was planning on using that as a stepping-stone to another more sane system.
DETAILS
What I realized while debugging that problem is that the SDL renderer for DirectX 11 does that when it creates a texture :
result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice,
&textureDesc,
NULL,
&textureData->mainTexture
);
The Microsoft's ID3D11Device::CreateTexture2D method page indicates that :
If you don't pass anything to pInitialData, the initial content of the memory for the resource is undefined. In this case, you need to write the resource content some other way before the resource is read.
If we're to believe that article :
Default Usage
The most common type of usage is default usage. To fill a default texture (one created with D3D11_USAGE_DEFAULT) you can :
[...]
After calling ID3D11Device::CreateTexture2D, use ID3D11DeviceContext::UpdateSubresource to fill the default texture with data from a pointer provided by the application.
So it looks like that D3D11_CreateTexture is using the second method of the default usage to initialize a texture and its content.
But right after that, in the SDL, we call SDL_UpdateTexture (without checking the return value ; I'll get to that later). If we dig until we get the the D3D11 renderer, we get that :
static int
D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Texture2D *texture, int bpp, int x, int y, int w, int h, const void *pixels, int pitch)
{
ID3D11Texture2D *stagingTexture;
[...]
/* Create a 'staging' texture, which will be used to write to a portion of the main texture. */
ID3D11Texture2D_GetDesc(texture, &stagingTextureDesc);
[...]
result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, &stagingTextureDesc, NULL, &stagingTexture);
[...]
/* Get a write-only pointer to data in the staging texture: */
result = ID3D11DeviceContext_Map(rendererData->d3dContext, (ID3D11Resource *)stagingTexture, 0, D3D11_MAP_WRITE, 0, &textureMemory);
[...]
/* Commit the pixel buffer's changes back to the staging texture: */
ID3D11DeviceContext_Unmap(rendererData->d3dContext, (ID3D11Resource *)stagingTexture, 0);
/* Copy the staging texture's contents back to the texture: */
ID3D11DeviceContext_CopySubresourceRegion(rendererData->d3dContext, (ID3D11Resource *)texture, 0, x, y, 0, (ID3D11Resource *)stagingTexture, 0, NULL);
SAFE_RELEASE(stagingTexture);
return 0;
}
Note : code snipped for conciseness.
This seems to indicate, based on that article I mentioned, that SDL is using the second method of the Default Usage to allocate the texture memory on the GPU, but uses the Staging Usage to upload the actual pixels.
I don't know that much about DX11 programming, but that mixing up of techniques got my programmer's sense tingling.
I contacted a game programmer I know and explained the problem to him. He told me the following interesting bits :
The driver gets to decide where it's storing staging textures. It usually lies in CPU RAM.
It's much better to specify a pInitialData pointer, as the driver can decide to upload the textures asynchronously.
If you load too many staging textures without commiting them to the GPU, you can fill up the RAM.
I then wondered why SDL didn't return me a "out of memory" error at the time I called SDL_CreateTextureFromSurface, and I found out why (again, snipped for concision) :
SDL_Texture *
SDL_CreateTextureFromSurface(SDL_Renderer * renderer, SDL_Surface * surface)
{
[...]
SDL_Texture *texture;
[...]
texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC,
surface->w, surface->h);
if (!texture) {
return NULL;
}
[...]
if (SDL_MUSTLOCK(surface)) {
SDL_LockSurface(surface);
SDL_UpdateTexture(texture, NULL, surface->pixels, surface->pitch);
SDL_UnlockSurface(surface);
} else {
SDL_UpdateTexture(texture, NULL, surface->pixels, surface->pitch);
}
[...]
return texture;
}
If the creation of the texture is successful, it doesn't care whether or not it succeeded in updating the textures (no check on SDL_UpdateTexture's return value).
WORKAROUND
The poor-man's workaround to that problem is to call SDL_RenderPresent each time you call a SDL_CreateTextureFromSurface.
It's probably fine to do it once every hundred textures depending on your texture size. But just be aware that calling SDL_CreateTextureFromSurface repeatedly without updating the renderer will actually fill up the system RAM, and the SDL won't return you any error condition to check for this.
The irony of this is that had I implemented a "correct" loading loop with percentage of completion on screen, I would never had that problem. But fate had me implement this the quick-and-dirty way, as a proof of concept for a bigger system, and I got sucked into that problem.

Get 32 bit RGBA image from Windows clipboard

I want my app (which works with RGBA8888 images) to be able to paste images from the Windows clipboard. So it should be able to read images off the clipboard that come from any common raster image apps like Gimp, Photoshop, MSPaint, etc.
From reading up on the clipboard functions, it seems I should be able to call GetClipboardData(CF_DIBV5) to get access to pretty much any bitmap type that's on the Clipboard since Windows automatically converts between that and CF_BITMAP and CF_DIB. But from reading up on the DIB format, I see that there is an immense number of possible combinations of bit depth, RGB order, optional compression, etc. It seems like what I'm doing would be a common task, but I don't see any conversion functions in the Windows API (unless I'm poor at searching), and this seems like something that would take a week to write to support all possible formats. So I'm wondering if I've overlooked something obvious. Or if there is some kind of assumption I can make to simplify this...like if all the popular image apps happen to copy images to the clipboard in uncompressed/unindexed formats.
UPDATE: Here's what I have so far:
HGLOBAL clipboard = GetClipboardData(CF_DIBV5);
exists = clipboard != NULL;
int dataLength = GlobalSize(clipboard);
exists = dataLength != 0;
if (exists) {
LPTSTR lockedClipboard = GlobalLock(clipboard);
exists = lockedClipboard != NULL;
if (exists) {
BITMAPV5HEADER *header = (BITMAPV5HEADER*)lockedClipboard;
LONG width = header->bV5Width;
LONG height = header->bV5Height;
BYTE *bits = header + sizeof(header) + header->bV5ClrUsed * sizeof(RGBQUAD);
//Now what? Need function to convert the bits to something uncompressed.
GlobalUnlock(clipboard);
}
}
UPDATE 2:
To clarify, I need literally uncompressed 32 bit image data (RRGGBBAA) which I can manipulate however I like in a cross-platform app. I have no need to use Windows APIs to draw this image to screen.
I am aware of a 3rd party library called stdb_image.h that can load .bmps, .jpgs, and .pngs into the type of data I need. So if there's a way I can turn the clipboard data into bitmap or png file data without losing alpha, then I'll be in good shape.
The basic strategy I've found is to check if there's a raw PNG on the clipboard and use that first if available. That's the easiest. Some apps, such as GIMP, copy images as PNG to the clipboard.
Then check for CF_DIBV5. The location of the actual bits depends on whether the "compression" is BI_BITFIELDS:
int offset = bitmapV5Header->bV5Size + bitmapV5Header->bV5ClrUsed * (bitmapV5Header->bV5BitCount > 24 ? sizeof(RGBQUAD) : sizeof(RGBTRIPLE));
if (compression == BI_BITFIELDS)
offset += 12; //bit masks follow the header
BYTE *bits = (BYTE*)bitmapV5Header + offset;
If the header says compression is BI_BITFIELDS, then the data is already as I needed it.
If the header says compression is BI_RGB and the bit count is 24 or 32, then I can unpack the bytes. 24 bytes means row size might not land on a DWORD boundary, so you have to watch for that.
Finally, lower bit counts than 24 likely mean indexed color, which I don't have working yet.
Here is example of usage for CF_DIBV5 and CF_DIB. It's best to use CF_DIB as backup option. Note, this code won't work for palette based images (if it is not guaranteed 32bit then see the method further down)
You can use SetDIBitsToDevice to draw directly on HDC, or use SetDIBits
GDI functions don't support alpha transparency (except for a couple of functions like TransparentBlt), in general you have to use libraries such as GDI+ for that.
void foo(HDC hdc)
{
if (!OpenClipboard(NULL))
return;
HANDLE handle = GetClipboardData(CF_DIBV5);
if (handle)
{
BITMAPV5HEADER* header = (BITMAPV5HEADER*)GlobalLock(handle);
if (header)
{
BITMAPINFO bmpinfo;
memcpy(&bmpinfo.bmiHeader, header, sizeof(BITMAPINFOHEADER));
bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFO);
//(use `header` to access other BITMAPV5HEADER information)
int w = bmpinfo.bmiHeader.biWidth;
int h = bmpinfo.bmiHeader.biHeight;
const char* bits = (char*)(header) + header->bV5Size;
//draw using SetDIBitsToDevice
SetDIBitsToDevice(hdc,0,0,w,h,0,0,0,h,bits,&bmpinfo,DIB_RGB_COLORS);
}
}
else
{
handle = GetClipboardData(CF_DIB);
if (handle)
{
BITMAPINFO* bmpinfo = (BITMAPINFO*)GlobalLock(handle);
if (bmpinfo)
{
int w = bmpinfo->bmiHeader.biWidth;
int h = bmpinfo->bmiHeader.biHeight;
const char* bits = (char*)(bmpinfo)+bmpinfo->bmiHeader.biSize;
SetDIBitsToDevice(hdc, 0, 0, w, h, 0, 0, 0, h, bits, bmpinfo, 0);
}
}
}
CloseClipboard();
}
If the original image is palette based, you would have to convert to 32bit. Alternatively you could add BITMAPFILEHEADER to the data (assuming the source is bitmap) then pass to the other library.
This is an example using CreateDIBitmap and GetDIBits to make sure the pixels are in 32bit:
HANDLE handle = GetClipboardData(CF_DIB);
if (handle)
{
BITMAPINFO* bmpinfo = (BITMAPINFO*)GlobalLock(handle);
if (bmpinfo)
{
int offset = (bmpinfo->bmiHeader.biBitCount > 8) ?
0 : sizeof(RGBQUAD) * (1 << bmpinfo->bmiHeader.biBitCount);
const char* bits = (const char*)(bmpinfo)+bmpinfo->bmiHeader.biSize + offset;
HBITMAP hbitmap = CreateDIBitmap(hdc, &bmpinfo->bmiHeader, CBM_INIT, bits, bmpinfo, DIB_RGB_COLORS);
//convert to 32 bits format (if it's not already 32bit)
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
int w = bm.bmWidth;
int h = bm.bmHeight;
char *bits32 = new char[w*h*4];
BITMAPINFOHEADER bmpInfoHeader = { sizeof(BITMAPINFOHEADER), w, h, 1, 32 };
HDC hdc = GetDC(0);
GetDIBits(hdc, hbitmap, 0, h, bits32, (BITMAPINFO*)&bmpInfoHeader, DIB_RGB_COLORS);
ReleaseDC(0, hdc);
//use bits32 for whatever purpose...
//cleanup
delete[]bits32;
}
}

libjpeg: process all scanlines once

I use jpeg library v8d from Independent JPEG Group and I want to change the way jpeg decompression reads and processes data.
In the djpeg main(), only one scanline/row at a time is read and processed in each jpeg_read_scanlines() call. So, to read entire image this functions is called until all lines are read and processed:
while (cinfo.output_scanline < cinfo.output_height) {
num_scanlines = jpeg_read_scanlines(&cinfo, dest_mgr->buffer,
dest_mgr->buffer_height); //read and process
(*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, num_scanlines); //write to file
}
But I would like to read the entire image once and store it in the memory and then process the entire image from memory. By reading libjpeg.txt, I found out this is possible: "You can process an entire image in one call if you have it all in memory, but usually it's simplest to process one scanline at a time."
Even though I made some progress, I couldn't make it completely work. I can now read a couple of rows once by increasing pub.buffer_height value and pub.buffer size, but no matter how large pub.buffer_height and pub.buffer are, only a couple of lines are read in each jpeg_read_scanlines() call. Any thoughts on this?
only a couple of lines are read in each jpeg_read_scanlines()
Yes, so you call it in a loop. Here's a loop that grabs one scanline at a time:
unsigned char *rowp[1], *pixdata = ...;
unsigned rowbytes = ..., height = ...;
while (cinfo.output_scanline < height) {
rowp[0] = pixdata + cinfo.output_scanline * rowbytes;
jpeg_read_scanlines(&cinfo, rowp, 1);
}
Once the loop exits, you have the entire image.

Need to write algorithm in state-machine style, but it becomes very hard to read

I work on embedded device's firmware (write in C), I need to take a screenshot from the display and save it as a bmp file. Currently I work on the module that generates bmp file data. The easiest way to do that is to write some function that takes the following arguments:
(for simplicity, only images with indexed colors are supported in my example)
color_depth
image size (width, height)
pointer to function to get palette color for color_index (i)
pointer to function to get color_index of the pixel with given coords (x, y)
pointer to function to write image data
And then user of this function should call it like that:
/*
* Assume we have the following functions:
* int_least32_t palette_color_get (int color_index);
* int pix_color_idx_get (int x, int y);
* void data_write (const char *p_data, size_t len);
*/
bmp_file_generate(
1, //-- color_depth
x, y, //-- size
palette_color_get,
pic_color_idx_get,
data_write
);
And that's it: this functions does all the job, and returns only when job is done (i.e. bmp file generated and "written" by given user callback function data_write().
BUT, I need to make bmp_writer module to be usable in cooperative RTOS, and data_write() might be a function that actually transmits data via some protocol (say, UART) to another device), so, this function needs to be called only from Task context. This approach doesn't work then, I need to make it in OO-style, and its usage should look like this:
/*
* create instance of bmp_writer with needed params
* (we don't need "data_write" pointer anymore)
*/
T_BmpWriter *p_bmp_writer = new_bmp_writer(
1, //-- color_depth
x, y, //-- size
palette_color_get,
pic_color_idx_get
);
/*
* Now, byte-by-byte get all the data!
*/
while (bmp_writer__data_available(p_bmp_writer) > 0){
char cur_char = bmp_writer__get_next_char(p_bmp_writer);
//-- do something useful with current byte (i.e. cur_char).
// maybe transmit to another device, or save to flash, or anything.
}
/*
* Done! Free memory now.
*/
delete_bmp_writer(p_bmp_writer);
As you see, user can call bmp_writer__get_next_char(p_bmp_writer) when he need that, and handle received data as he wants.
Actually I already implemented this, but, with that approach, all the algorithm becomes turned inside out, and this code is extremely non-readable.
I'll show you a part of old code that generates palette data (from the function that does all the job, and returns only when job is done), and appropriate part of new code (in state-machine style).
Old code:
void bmp_file_generate(/*....args....*/)
{
//-- ... write headers
//-- write palette (if needed)
if (palette_colors_cnt > 0){
size_t i;
int_least32_t cur_color;
for (i = 0; i < palette_colors_cnt; i++){
cur_color = callback_palette_color_get(i);
callback_data_write((const char *)&cur_color, sizeof(cur_color));
}
}
//-- ...... write image data ..........
}
As you see, very short and easy-readable code.
Now, new code.
It looks like state-machine, because it's actually splitted by stages (HEADER_WRITE, PALETTE_WRITE, IMG_DATA_WRITE), each stage has its own context. In the old code, context was saved in local variables, but now we need to make the structure and allocate it from heap.
So:
/*
* Palette stage context
*/
typedef struct {
size_t i;
size_t cur_color_idx;
int_least32_t cur_color;
} T_StageContext_Palette;
/*
* Function that switches stage.
* T_BmpWriter is an object context, and pointer *me is analogue of "this" in OO-languages.
* bool_start is 1 if stage is just started, and 0 if it is finished.
*/
static void _stage_start_end(T_BmpWriter *me, U08 bool_start)
{
switch (me->stage){
//-- ...........other stages.........
case BMP_WR_STAGE__PALETTE:
if (bool_start){
//-- palette stage is just started. Allocate stage context and initialize it.
me->p_stage_context = malloc(sizeof(T_StageContext_Palette));
memset(me->p_stage_context, 0x00, sizeof(T_StageContext_Palette));
//-- we need to get first color, so, set index of byte in cur_color to maximum
((T_StageContext_Palette *)me->p_stage_context)->i = sizeof(int_least32_t);
} else {
free(me->p_stage_context);
me->p_stage_context = NULL;
}
break;
//-- ...........other stages.........
}
}
/*
* Function that turns to the next stage
*/
static void _next_stage(T_BmpWriter *me)
{
_stage_start_end(me, 0);
me->stage++;
_stage_start_end(me, 1);
}
/*
* Function that actually does the job and returns next byte
*/
U08 bmp_writer__get_next_char(T_BmpWriter *me)
{
U08 ret = 0; //-- resulting byte to return
U08 bool_ready = 0; //-- flag if byte is ready
while (!bool_ready){
switch (me->stage){
//-- ...........other stages.........
case BMP_WR_STAGE__PALETTE:
{
T_StageContext_Palette *p_stage_context =
(T_StageContext_Palette *)me->p_stage_context;
if (p_stage_context->i < sizeof(int_least32_t)){
//-- return byte of cur_color
ret = *( (U08 *)&p_stage_context->cur_color + p_stage_context->i );
p_stage_context->i++;
bool_ready = 1;
} else {
//-- need to get next color (or even go to next stage)
if (p_stage_context->cur_color_idx < me->bmp_details.palette_colors_cnt){
//-- next color
p_stage_context->cur_color = me->callback.p_palette_color_get(
me->callback.user_data,
p_stage_context->cur_color_idx
);
p_stage_context->cur_color_idx++;
p_stage_context->i = 0;
} else {
//-- next stage!
_next_stage(me);
}
}
}
break;
//-- ...........other stages.........
}
}
return ret;
}
So huge code, and it's so hard to understand it!
But I really have no idea how to make it in some different way, to be able to get information byte-by-byte.
Does anyone know how to achieve this, and keep code readability?
Any help is appreciated.
You can try protothread, which is useful to transform a state-machine based program into thread-style program. I'm not 100% sure that it can solve your problem elegantly, you can give it a try. The paper is a good starting point: Protothreads: simplifying event-driven programming of memory-constrained embedded systems
Here is its source code: http://code.google.com/p/protothread/
By the way, protothread is also used in the Contiki embedded OS, for implementing process in Contiki.

Resources