• Print

Author Topic: direct access to images?  (Read 347 times)

SkyCharger001

  • Hero Member
  • *****
  • Posts: 1601
direct access to images?
« on: December 30, 2012, 04:04:27 PM »
I'm working on porting a 3d-graphics-library from QB to C++ for QB64-SDL.
While it works as is (and shows a noticeable improvement), it still suffers from a few bottlenecks.

the biggest problem comes from the frequent times it has to call sub__dest() (the c++ side of _DEST) as it has to work with 2/3 images simultaneous. (zbuffer, visual image and a hotspotfield)
Is there any way to access the images directly using C++? (the reduced overhead should improve the framerate)

PS I know this isn't the most safe thing to do, but most of the potential problems are already handled by the wrapper.

mcalkins

  • Hero Member
  • *****
  • Posts: 1279
    • qbasicmichael.com
    • Email
Re: direct access to images?
« Reply #1 on: December 31, 2012, 10:33:18 AM »
Do you mean _MEMIMAGE?

I used it the other day to access a SCREEN 13 image.:
http://www.qb64.net/forum/index.php?topic=10174.msg82995#msg82995

Regards,
Michael
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

SkyCharger001

  • Hero Member
  • *****
  • Posts: 1601
Re: direct access to images?
« Reply #2 on: December 31, 2012, 03:11:08 PM »
_MEMGET has a direct C++ equivalent, _MEMPUT does not.
but it has already improved the frame-rate by ~10%.

I've tried transplanting the pointer arithmetic that replaces a _MEMPUT call used in the "main.txt" of a small program (an 'easy' way of exposing the C++ side of a qb64 internal function/statement), but it causes an immediate crash.

mcalkins

  • Hero Member
  • *****
  • Posts: 1279
    • qbasicmichael.com
    • Email
Re: direct access to images?
« Reply #3 on: December 31, 2012, 04:17:38 PM »
Both _MEMGET and _MEMPUT can be replaced with normal pointer dereferencing.

Code: [Select]
$CHECKING:OFF
s
SYSTEM

SUB s
DIM i AS LONG
DIM m AS _MEM
SCREEN 13
m = _MEMIMAGE
FOR i = 0 TO 15
 _MEMPUT m, m.OFFSET + i, i AS _UNSIGNED _BYTE
NEXT
_DELAY 5
_MEMFREE m
END SUB

results in:

main.txt
Code: [Select]
S_0:;
SUB_S();
close_program=1;
end();
sub_end();
return NULL;
}
void SUB_S(){
qbs *tqbs;
ptrszint tmp_long;
int32 tmp_fileno;
uint32 qbs_tmp_base=qbs_tmp_list_nexti;
uint8 *tmp_mem_static_pointer=mem_static_pointer;
uint32 tmp_cmem_sp=cmem_sp;
#include "data1.txt"
if (new_error) goto exit_subfunc;
mem_lock *sf_mem_lock;
new_mem_lock();
sf_mem_lock=mem_lock_tmp;
sf_mem_lock->type=3;
qbg_screen( 13 ,NULL,NULL,NULL,NULL,1);
memcpy((((char*)_SUB_S_UDT_M)+(0)),((char*)&(func13=func__memimage(NULL,0))),28);
fornext_value15= 0 ;
fornext_finalvalue15= 15 ;
fornext_step15= 1 ;
if (fornext_step15<0) fornext_step_negative15=1; else fornext_step_negative15=0;
if (new_error) goto fornext_error15;
goto fornext_entrylabel15;
while(1){
fornext_value15=fornext_step15+(*_SUB_S_LONG_I);
fornext_entrylabel15:
*_SUB_S_LONG_I=fornext_value15;
if (fornext_step_negative15){
if (fornext_value15<fornext_finalvalue15) break;
}else{
if (fornext_value15>fornext_finalvalue15) break;
}
fornext_error15:
*(uint8*)(*(ptrszint*)(((char*)_SUB_S_UDT_M)+(0))+*_SUB_S_LONG_I)=*_SUB_S_LONG_I;
}
fornext_exit_14:;
sub__delay( 5 );
sub__memfree((void*)( ((char*)(_SUB_S_UDT_M)) + (0) ));
exit_subfunc:;
free_mem_lock(sf_mem_lock);
#include "free1.txt"
if ((tmp_mem_static_pointer>=mem_static)&&(tmp_mem_static_pointer<=mem_static_limit)) mem_static_pointer=tmp_mem_static_pointer; else mem_static_pointer=mem_static;
cmem_sp=tmp_cmem_sp;
}

data1.txt:
Code: [Select]
int32 *_SUB_S_LONG_I=NULL;
if(_SUB_S_LONG_I==NULL){
_SUB_S_LONG_I=(int32*)mem_static_malloc(4);
*_SUB_S_LONG_I=0;
}
void *_SUB_S_UDT_M=NULL;
if(_SUB_S_UDT_M==NULL){
_SUB_S_UDT_M=(void*)mem_static_malloc(28);
memset(_SUB_S_UDT_M,0,28);
}
mem_block func13;
int64 fornext_value15;
int64 fornext_finalvalue15;
int64 fornext_step15;
uint8 fornext_step_negative15;
byte_element_struct *byte_element_16=NULL;
if (!byte_element_16){
if ((mem_static_pointer+=12)<mem_static_limit) byte_element_16=(byte_element_struct*)(mem_static_pointer-12); else byte_element_16=(byte_element_struct*)mem_static_malloc(12);
}

So, you can do something like this:

delme1.h
Code: [Select]
void sub__delay(double seconds);               // qbx.cpp includes regsf.txt before declaring sub__delay.

void Sub_s(void) {
 uint8 *tmp_mem_static_pointer=mem_static_pointer;
 uint32 tmp_cmem_sp=cmem_sp;
 mem_lock *sf_mem_lock;
 new_mem_lock();
 sf_mem_lock=mem_lock_tmp;
 sf_mem_lock->type=3;

 qbg_screen(13 ,0, 0, 0, 0, 1);
 mem_block m = func__memimage(0, 0);
 uint8 * p = (uint8 *) m.offset;               // set up an unsigned char * with the value of the offset from the mem_block.
 
 for (int i = 0; i <= 15; ++i) {
  p[i] = i;                                    // dereference the unsigned char *
 }

 sub__delay(5);
 sub__memfree(& m);

 exit_subfunc:;
 free_mem_lock(sf_mem_lock);
 if ((tmp_mem_static_pointer>=mem_static)&&(tmp_mem_static_pointer<=mem_static_limit)) mem_static_pointer=tmp_mem_static_pointer; else mem_static_pointer=mem_static;
 cmem_sp=tmp_cmem_sp;
}

Code: [Select]
DECLARE LIBRARY "delme1"
 SUB Sub_s
END DECLARE

Sub_s
SYSTEM

Regards,
Michael
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

SkyCharger001

  • Hero Member
  • *****
  • Posts: 1601
Re: direct access to images?
« Reply #4 on: December 31, 2012, 05:56:38 PM »

"mem_block m = func__memimage(0, 0);"
"sub__memfree(& m);"
I understand

pointer dereferencing:
"*(uint8*)(*(ptrszint*)((cimg.offset+position)))=r;" (compiler says OK, but causes crash when uncommented, most likely due to the fact that the image is presumably still locked)
I THINK I understand

what purpose is mem_lock and how does it work?


mcalkins

  • Hero Member
  • *****
  • Posts: 1279
    • qbasicmichael.com
    • Email
Re: direct access to images?
« Reply #5 on: December 31, 2012, 08:48:13 PM »
Did you try the code that I posted? It should set 16 pixels in the top row of SCREEN 13.

Both mem_block and mem_lock are defined in common.cpp:

Code: [Select]
struct mem_block{
ptrszint offset;
ptrszint size;
int64 lock_id;//64-bit key, must be present at lock's offset or memory region is invalid
ptrszint lock_offset;//pointer to lock
int32 type;
/*
memorytype (4 bytes, but only the first used, for flags):
1 integer values
2 unsigned (set in conjunction with integer)
4 floating point values
8 char string(s) 'element-size is the memory size of 1 string
*/
ptrszint elementsize;
};
struct mem_lock{
uint64 id;
int32 type;//required to know what action to take (if any) when a request is made to free the block
//0=no security (eg. user defined block from _OFFSET)
//1=C-malloc'ed block
//2=image
//3=sub/function scope block
//4=array
//---- type specific variables follow ----
void *offset;//used by malloc'ed blocks to free them
};

My understanding of mem_lock and the lock_id is rather vague.

I'm thinking that every mem_block gets a mem_lock. Also, each SUB/FUNCTION gets a mem_lock.

The mem_lock describes what kind of block it is (arbitrary, malloc'ed, etc...). It has a 64 bit id number. You can see from the implementation of new_mem_lock near the start of libqbx.cpp, that each lock id number is generated by incrementation. I'm thinking that the id number is mainly used with $CHECKING:ON as a safety check. (The id in the mem_block would be compared with the id in the mem_lock.) I don't think that it has any other purpose. If the mem_lock is associated with malloc'ed memory, there is a pointer to use with free.

You can see in libqbx.cpp that the first thing that free_mem_lock does is clear the id number.

I'm not sure exactly what role the SUB/FUNCTION mem_lock has. I'm thinking that it's for safety checking that local data pointed to by mem_blocks hasn't gone out of scope. I don't know how it works. I could be wrong.

Look at the implementation of func__memimage in libqbx.cpp. When we call func__memimage(0, 0), it does:
 im=write_page;
Where im is a img_struct * with local scope, and write_page is a img_struct * with file scope, pointing to the img_struct for the current destination screen image.
It then sets the mem_block's lock_offset to point to the img_struct's mem_lock, creating one for it if it didn't already have one.
It then sets the mem_block's offset to the value of the img_struct's offset, as well as setting the size, type, and elementsize members.
It then returns the structure. (The caller owns the structure. The caller passed a pointer to the structure as a hidden parameter to the function.)

QB64 takes a roundabout way of storing the mem_block that it receives from func__memimage. It uses memcpy instead of C++'s built in structure assignment. (QB64 also uses a temporary mem_block. I think that this is unnecessarily roundabout.)

sub__memfree in libqbx.cpp calls free_mem_lock, and invalidates the mem_block's lock_id.



Consider this:

This code would crash, as m is not initialized. It is only for analyzing the generated code.
Code: [Select]
$CHECKING:OFF
DIM m AS _MEM
_MEMPUT m, m.OFFSET, 3 AS _UNSIGNED _BYTE

It results in:

global.txt:
Code: [Select]
void *__UDT_M=NULL;
int32 console=0;
int32 screen_hide_startup=0;
ptrszint data_size=0;
uint8 *data=(uint8*)calloc(1,1);

maindata.txt:
Code: [Select]
if(__UDT_M==NULL){
__UDT_M=(void*)mem_static_malloc(28);
memset(__UDT_M,0,28);
}
byte_element_struct *byte_element_35=NULL;
if (!byte_element_35){
if ((mem_static_pointer+=12)<mem_static_limit) byte_element_35=(byte_element_struct*)(mem_static_pointer-12); else byte_element_35=(byte_element_struct*)mem_static_malloc(12);
}

main.txt:
Code: [Select]
S_0:;
*(uint8*)(*(ptrszint*)(((char*)__UDT_M)+(0)))= 3 ;
sub_end();
return NULL;
}

__UDT_M is a mem_block *. It will point to 28 bytes from mem_static_malloc.

(Note that we aren't initializing the mem_block with anything. Had we done so, a function returning a mem_block would have been called, returning a memblock to a temporary mem_block, which would then be memcpy'ed to the mem_block pointed to by __UDT_M. (Yes, that is unnecessarily roundabout.))

_MEMPUT m, m.OFFSET, 3 AS _UNSIGNED _BYTE is implemented inline as:

*(uint8*)(*(ptrszint*)(((char*)__UDT_M)+(0)))= 3 ;

Remember that __UDT_M is not a mem_block. It is a pointer to a mem_block. That is why there are two dereference/indirection operations happening:

__UDT_M  is a pointer to a mem_block.

(((char*)__UDT_M)+(0))  This typecasting and pointer arithmatic is selecting a pointer to the first byte of the mem_block. (Note, we are still dealing with the _MEM type, not with the screen data.) I'm thinking that the awkwardness of this syntax is due to QB64 treating a mem_block as a BASIC User Defined Type.

(ptrszint*)(((char*)__UDT_M)+(0))  We still have a pointer to the start of the mem_block, but now we're casting it to a ptrszint *. So, we now have a pointer to a pointer-sized-int.

(*(ptrszint*)(((char*)__UDT_M)+(0)))  Now we're dereferencing it. We are effectively dereferencing the mem_block * as if it were a ptrszint *. Note that the offset member is a ptrszint at the start of the mem_block. This could be rewritten as:  __UDT_M->offset  . So, now we have the offset to the data itself, in the form of a pointer-sized integer.

*(uint8*)(*(ptrszint*)(((char*)__UDT_M)+(0)))  We are now casting the offset to a uint8 *, and dereferencing it. This could be rewritten as:  *((uint8 *)__UDT_M->offset)  .

*(uint8*)(*(ptrszint*)(((char*)__UDT_M)+(0)))= 3  This assigns 3 to the uint8 pointed to by __UDT_M->offset. It could be rewritten as:  *((uint8 *)__UDT_M->offset) = 3  .

Quote
"*(uint8*)(*(ptrszint*)((cimg.offset+position)))=r;" (compiler says OK, but causes crash when uncommented, most likely due to the fact that the image is presumably still locked)

Assuming cimg is a mem_block, you shouldn't be using 2 dereferences, only 1. You are reading a ptrszint from the data, casting it to a uint8 *, and dereferencing it again. As it is probably not a valid pointer, you are probably causing an access violation exception.

Try changing it to:

Code: [Select]
((uint8 *) cimg.offset)[position] = r;


Note that if we could access write_page, we wouldn't need to call func__memimage. It is declared in libqbx.cpp at file scope, and is extern by default. However, qbx.cpp does not import it. However, we can declare it ourselves.

delme1.h
Code: [Select]
void sub__delay(double seconds);               // qbx.cpp includes regsf.txt before declaring sub__delay.

void Sub_s(void) {
 uint8 *tmp_mem_static_pointer=mem_static_pointer;
 uint32 tmp_cmem_sp=cmem_sp;
 mem_lock *sf_mem_lock;
 new_mem_lock();
 sf_mem_lock=mem_lock_tmp;
 sf_mem_lock->type=3;

 qbg_screen(13 ,0, 0, 0, 0, 1);
 extern img_struct * write_page;               // qbx.cpp does not declare write_page.
 uint8 * p = (uint8 *) write_page->offset;
 
 for (int i = 0; i <= 15; ++i) {
  p[i] = i;                                    // dereference the unsigned char *
 }

 sub__delay(5);

 exit_subfunc:;
 free_mem_lock(sf_mem_lock);
 if ((tmp_mem_static_pointer>=mem_static)&&(tmp_mem_static_pointer<=mem_static_limit)) mem_static_pointer=tmp_mem_static_pointer; else mem_static_pointer=mem_static;
 cmem_sp=tmp_cmem_sp;
}

Code: [Select]
DECLARE LIBRARY "delme1"
 SUB Sub_s
END DECLARE

Sub_s
SYSTEM

(I didn't do this with my code in fatman's topic (link in my other post), because I wrote it for the qbxrt.dll hacked version ( http://www.qb64.net/forum/index.php?topic=10071.msg81792#msg81792 ) that I made earlier this month, and qbxrt.dll does not export write_page as a DLL symbol. I later modified it for regular QB64 by adding the declaration for sub__limit.)

Regards,
Michael

P.S. If you're wanting to increase the execution speed of your C++ code, I suggest compiling it with optimization enabled. You would probably want to also recompile libqbx (see makelib.bat) with optimization enabled.

To enable optimization, add either -O2 or -O3 to the compiler command line. Note that compiler optimization can break code that has undefined behavior. I'm not sure how much this would affect the code used by QB64. My guess is that the probable trouble spots might be "signed integer overflow" and "strict aliasing". So, you might use -fno-strict-overflow and -fno-strict-aliasing to selectively disable those optimizations.

http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Optimize-Options.html

P.S.
Quote from: SkyCharger001 on December 30, 2012, 04:04:27 PM
the biggest problem comes from the frequent times it has to call sub__dest() (the c++ side of _DEST) as it has to work with 2/3 images simultaneous. (zbuffer, visual image and a hotspotfield)
Is there any way to access the images directly using C++? (the reduced overhead should improve the framerate)

 :-[ I misunderstood / didn't think that through. So far, I've been assuming just one screen image.

With the inlining and other optimizations (see previous P.S.), the call to sub__dest shouldn't be so bad.

As you can see in sub__dest, there is a call to validatepage, followed by:
Code: [Select]
write_page_index=i; write_page=&img[i];You could probably do those assignments yourself, declaring the extern variables as needed.
I don't know how necessary it is to call validatepage. There seems to be code in there beyond just error checking, but I don't know if _DEST needs it.

I earlier ignored the code in func__memimage that would be used if you specified a screen image handle. Also, writepage would not be very directly useful to you.

You could get the pointers from all 3 mem images once and then store them. I'm guessing that's what you already planned to do.

Regards,
Michael

P.S. The optimization should be better if your code and libqbx are compiled together, rather than as separate steps. In fact, if they are compiled separately, there would be no way to inline the functions from one to the other, without the function definition appearing in both. Even if you compile them together, I'm not sure if it would do it without the function definition appearing in both compilation units. It might.
« Last Edit: January 01, 2013, 07:37:42 AM by mcalkins »
The QBASIC Forum Community: http://www.network54.com/index/10167 Includes off-topic subforums.
QB64 Off-topic subforum: http://qb64offtopic.freeforums.org/

SkyCharger001

  • Hero Member
  • *****
  • Posts: 1601
Re: direct access to images?
« Reply #6 on: January 01, 2013, 03:30:04 AM »
"((uint8 *) cimg.offset)[position] = r;"
turned out to be all I needed.

Galleon

  • Administrator
  • Hero Member
  • *****
  • Posts: 4679
  • QB Forever
    • Email
Re: direct access to images?
« Reply #7 on: January 03, 2013, 03:57:55 AM »
My 2c for what it's worth.
#1) QB64-GL will be able to do 3D graphics in a few weeks time (or sooner?), so bear that in mind.
#2) Create your C functions in a file called 'my_helpers.h' then use DECLARE LIBRARY "my_helpers" to access your functions. Use the _OFFSET type to pass the pointers to the C functions. Use _MEM to get the offset of images then my_mem.OFFSET to find the offset of the pixels.
Something old... Something new... Something borrowed... Something blue...

SkyCharger001

  • Hero Member
  • *****
  • Posts: 1601
Re: direct access to images?
« Reply #8 on: January 03, 2013, 09:44:08 AM »
I've always had that in mind as the library is only meant as a stop-gap for those waiting for the official release of QB64-GL and envisioned as SDL-only.

  • Print