SDK Question How to blit onto an MFD's display surface?

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
I'm displaying a 2D representation of a vessel's mesh inside an MFD:

Deltaglider_2D_Mesh.png

Obviously saving all the lines into an array and drawing them with oapi::Sketchpad's Line function is proving quite costly.

Reducing the number of sampled vertices will improve performance, but not enough. I think the best way is to draw the mesh onto some surface in memory, and blit this onto the MFD at each update.

Creating a SURFHANDLE and drawing on it with oapi::Sketchpad has (seemingly) worked fine. However, I'm having a problem blitting this onto the MFD. In MFD2::Update, I am passed an oapi::Sketchpad* skp, which has a function GetSurface() - this should give me the MFD's display surface. The problem is using oapiBlt to blit the mesh onto the display surface... nothing gets displayed.

I found a note in the API docs, and a mention of it in this thread:

jarmonik said:
There is a note in the API documentation of oapiBlt():
"This function must not be used while a device context is acquired for the target surface (i.e. between oapiGetDC and oapiReleaseDC calls)."

This means no oapiBlt calls in a callbacks those are using a sketchpad. In elsewhere a NULL should be a valid handle to blit in the backbuffer.

So it seems impossible from within the Update function.

It must still be possible without using GDI, since Martins replied to a very similar question here:

Martins said:
Assuming your bmp has been stored as a resource in your module:

- use the WindowsAPI LoadImage function to load the bitmap from the module. This will give you a HBITMAP handle.

- use oapiCreateSurface(HBITMAP) to create a surface from the bitmap

- use oapiBlt to copy the surface into the MFD's display surface

So my question is, how do I get the MFD's display surface some other way? Am I missing some function in the API?
 
Last edited:

jarmonik

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 28, 2008
Messages
2,666
Reaction score
795
Points
128
I belive Tschachim already gave you an answer in the other thread. If you are using a sketchpad then you might be able to get the surface using SketchPad::GetSurface() but I can't recommend that because it could conflict in a multible ways.
 

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
I belive Tschachim already gave you an answer in the other thread. If you are using a sketchpad then you might be able to get the surface using SketchPad::GetSurface() but I can't recommend that because it could conflict in a multible ways.

Tschachim's solution uses GDI. If possible, I'd like to avoid that for a number of reasons. I'd have to "downgrade" from MFD2 to MFD class, and then convert all my drawing code into GDI from oapi::Sketchpad. Also, it's... slow.

Yes, GetSurface() does not like being blitted onto at all, or any other kind of drawing operation it seems.

Thanks for the reply.
 

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,866
Reaction score
2,127
Points
203
Location
between the planets
I'd have to "downgrade" from MFD2 to MFD class

You wouldn't have to do that, actually. MFD2 still supports Update(HDC), the question would be which one gets called first... if Update(Sketchpad) gets called afterwards, or if there are no overlaps, you could use both of them in succession, blitting the bitmap with the GUI and drawing the rest with sketchpad.

It is a bit annoying that there is no Update(SURFHANDLE). After all, you could easily enough create a sketchpad for that, while the other way around doesn't work because there's no way to unlock the surface...

A question on the sideline: I see that you did something I thought about doing somewhere in the near future (drawing a mesh wireframe with sketchpad). Would you be willing to share the code for this?
 

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
You wouldn't have to do that, actually. MFD2 still supports Update(HDC)

Hm, are you sure? I've tried writing to Orbiter's log and oapiDebugString() from within Update(HDC), with no output from either. I also tried commenting out all of Update(Sketchpad), just in case Orbiter checked if that was implemented before calling Update(HDC), but still nothing in the log. I could've messed up somehow, that's always a possibility :p

jedidia said:
It is a bit annoying that there is no Update(SURFHANDLE). After all, you could easily enough create a sketchpad for that, while the other way around doesn't work because there's no way to unlock the surface...

Exactly. Perhaps it's cheaper for some reason to pass a sketchpad directly into the function... though I can't see why.

jedidia said:
A question on the sideline: I see that you did something I thought about doing somewhere in the near future (drawing a mesh wireframe with sketchpad). Would you be willing to share the code for this?

Of course. Drawing the mesh with sketchpad at each update is a big framerate killer though, so you'll have to do what I'm trying to do now - draw the mesh onto a bitmap in memory, and blit that at each update. If it's for a panel that shouldn't be a problem.

Code:
struct LINE 
{
	oapi::IVECTOR2 Points [2];
};

std::vector<LINE> Lines;

oapi::IVECTOR2 TransformPoint(VECTOR3 LocalPoint, oapi::IVECTOR2 Centre, double PixelsPerMetre)
{
	// We want XZ projection...
	oapi::IVECTOR2 point;
	point.x = Centre.x + int(PixelsPerMetre * LocalPoint.x);
	point.y = Centre.y - int(PixelsPerMetre * LocalPoint.z);
	return point;
}

// Call this each frame.
void CalcMeshPoints (double PixelsPerMetre)
{
	// Check we haven't already calculated the vertices, and that the vessel's visual is in range.
	if(!Lines.empty() || oapiCameraInternal() == true || oapiCameraTarget () != oapiGetFocusObject() || oapiCameraMode() == CAM_COCKPIT || oapiGetFocusInterface()->GetMeshCount() <= 0) return;
	
	// pV is focus vessel.
	// Get visual repreesntation of pV.
	VISHANDLE visptr = *oapiObjectVisualPtr(pV->GetHandle());

	if(visptr == NULL)
	{
		return;
	}
	
	// This is the centre point on the Sketchpad where the mesh will be drawn.
	oapi::IVECTOR2 centre;
	centre.x = long(0.5*GetWidth());
	centre.y = long(0.7 * GetHeight());
	
	MESHHANDLE mesh =  pV->GetMesh(visptr, 0);
	if(mesh == NULL) return;
	for(int i = 0; i < oapiMeshGroupCount(mesh); ++ i)
	{
		MESHGROUP* group = oapiMeshGroup(mesh, i);

		if(group == NULL) continue;

		for(int t = 1; t < group->nIdx; t++)
		{
			int id1 = group->Idx[t];
			int id2 = group->Idx[t-1];
			VECTOR3 v1 = _V(group->Vtx[id1].x, group->Vtx[id1].y, group->Vtx[id1].z);
			VECTOR3 v2 = _V(group->Vtx[id2].x, group->Vtx[id2].y, group->Vtx[id2].z);
			oapi::IVECTOR2 point1 = TransformPoint(v1, centre, PixelsPerMetre);
			oapi::IVECTOR2 point2= TransformPoint(v2, centre, PixelsPerMetre);
			LINE line = {{point1, point2}};
			Lines.push_back(line);
		}
	}
}


void Update(oapi::Sketchpad* skp)
{
	for(int i = 0; i < Parent_MFD->Lines.size(); ++ i)
	{
		skp->Line(Parent_MFD->Lines[i].Points[0].x, Parent_MFD->Lines[i].Points[0].y, Parent_MFD->Lines[i].Points[1].x, Parent_MFD->Lines[i].Points[1].y);
	}
}
 
Last edited:

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,866
Reaction score
2,127
Points
203
Location
between the planets
Hm, are you sure?

It's at least listed in the docummentation:

Code:
void MFD2::Update  ( HDC  hDC   )  [inline, virtual] 

Dummy implementation of GDI-specific base class method. 


Note:
Derived classes should overload the Update(oapi::Sketchpad*) method instead. 
Implements MFD.

Then again, on second read through, it says "overload the Update(Sketchpad) method instead". Might be that you can overload only either of them...?

Exactly. Perhaps it's cheaper for some reason to pass a sketchpad directly into the function... though I can't see why.

Could be that it never gets destroyed...

Hmmm, wait. A bit crazy, but might be worth a try: What happens if you get the surface from the passed sketchpad, then release the sketchpad with oapiReleaseSketchpad? might completely mess things up, or might just unlock the surface...

Of course. Drawing the mesh with sketchpad at each update is a big framerate killer though, so you'll have to do what I'm trying to do now - draw the mesh onto a bitmap in memory, and blit that at each update. If it's for a panel that shouldn't be a problem.

It is for a panel, yes, but I'll be facing the challenge of having to compose the schematic of several meshes, and it being panable and zoomable... I'm thinking about working with manipulating texture coordinates of the displaying meshgroup, should be a whole lot faster. Trouble of course is, it's going to be several colors :facepalm: I'll cross that bridge when I get there, first I have a release to get done, but something like this is worth experimenting with for an update. Thanks a lot, in any case!
 
Last edited:

kamaz

Unicorn hunter
Addon Developer
Joined
Mar 31, 2012
Messages
2,298
Reaction score
4
Points
0
Please have a look at the sources to ExtMFD or WebMFD or VNCMFD. They do the reverse thing, i.e. copy the MFD screen into bitmap. This is done in the following steps:

1. Create a bitmap compatible with the output device
2. Obtain HDC for the bitmap (bitmapHDC)
3. Obtain HDC for the screen's SURFHANDLE (screenHDC)
4. BitBlt from screenHDC to bitmap HDC
5. Release screenHDC and bitmapHDC.

So you could probably do the opposite -- i.e. draw your stuff to the bitmap and then BitBlt that bitmap into the display...
 

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
Hmmm, wait. A bit crazy, but might be worth a try: What happens if you get the surface from the passed sketchpad, then release the sketchpad with oapiReleaseSketchpad? might completely mess things up, or might just unlock the surface...

Yes! Thanks a lot, that did it!! :) :cheers:

For anyone's reference, this is what you have to do:
Code:
SURFHANDLE Surface = skp->GetSurface();
oapiReleaseSketchpad(skp);

// Draw on Surface here!

skp = oapiGetSketchpad(Surface);

// Here you can resume using skp.

I'm just finishing off the code that draws the mesh's image on a surface in memory, and later blits it to the display surface. I'll edit this post with the code when it's done.

It is for a panel, yes, but I'll be facing the challenge of having to compose the schematic of several meshes, and it being panable and zoomable... I'm thinking about working with manipulating texture coordinates of the displaying meshgroup, should be a whole lot faster. Trouble of course is, it's going to be several colors :facepalm: I'll cross that bridge when I get there, first I have a release to get done, but something like this is worth experimenting with for an update. Thanks a lot, in any case!

That sounds like a challenge, but I'm sure it won't be too difficult. oapiBlt has an argument RECT* src, the rectangle on the source surface to blit. You could resize that and move it around to pan/zoom.
 

jarmonik

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 28, 2008
Messages
2,666
Reaction score
795
Points
128
For anyone's reference, this is what you have to do:
Code:
SURFHANDLE Surface = skp->GetSurface();
oapiReleaseSketchpad(skp);

// Draw on Surface here!

skp = oapiGetSketchpad(Surface);

// Here you can resume using skp.

That's a bad idea. What is orbiter releasing after it's done MFD2::Update(skp) ?
 

Xyon

Puts the Fun in Dysfunctional
Administrator
Moderator
Orbiter Contributor
Addon Developer
Webmaster
GFX Staff
Beta Tester
Joined
Aug 9, 2009
Messages
6,926
Reaction score
793
Points
203
Location
10.0.0.1
Website
www.orbiter-radio.co.uk
Preferred Pronouns
she/her
XRRR's MFD does blitting with BitBlit just after the line that says
Code:
// Print a pretty picture of the XR to the MFD

Works fine for me from the Update(skp), anyway. Hope it can help you somehow.
 

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,866
Reaction score
2,127
Points
203
Location
between the planets
That's a bad idea. What is orbiter releasing after it's done MFD2::Update(skp) ?

That's why I called it a bit crazy. If oapiReleaseSkechtpad is secured against passing an invalid pointer, it should work. If not, it currently works by the sheer luck of undefined behavior and is a disaster waiting to happen. I would recommend doing an experiment with willingly calling oapiReleaseSketchpad with an invalid pointer and see what happens. If that causes a crash, then the current method is extremely unstable and relies on the uncertain hope that the new sketchpad will be allocated at the same place the old one was... :shifty:

Might be simplest to see what happens if you don't create a new sketchpad, but just release the old one in Update(). If that goes ok, you should be save.

does blitting with BitBlit just after the line that says. Works fine for me from the Update(skp), anyway.

I imagine it would, as the WinAPI doesn't blit directly into video memory. But how do you identify the context handle for the surface? you certainly can't pass a SURFHANDLE to BitBlt.
But this method would be a lot slower than the above, if the above can be proven to work reliably...

EDIT: ah... aaaaah... didn't realize you linked code at first, and I certainly didn't realize that sketchpad can pass you the device context handle itself, so there's definitely no need to use both versions of Update to get the frickin' handle.
This certainly wouldn't be too fast, but should still be faster than drawing the whole thing with sketchpad. A little...
 
Last edited:

Xyon

Puts the Fun in Dysfunctional
Administrator
Moderator
Orbiter Contributor
Addon Developer
Webmaster
GFX Staff
Beta Tester
Joined
Aug 9, 2009
Messages
6,926
Reaction score
793
Points
203
Location
10.0.0.1
Website
www.orbiter-radio.co.uk
Preferred Pronouns
she/her
I don't find it especially expensive in the limited testing I've done of it - and yeah the code link probably should've been more obvious. I'm open to suggestions on improving it though, it took forever to work out how to do it :p

My main gripe with it is that it's a WinAPI call, but at the time of writing I wasn't aware of oapiBlit.
 

jarmonik

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 28, 2008
Messages
2,666
Reaction score
795
Points
128
It is possible that in the future a graphics clients could provide an extended version of the sketchpad that contains a CopyRect function for blitting with color key and alpha blend support. I belive it might be possible to extend the inline engine to support that as well.
 

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,866
Reaction score
2,127
Points
203
Location
between the planets
I belive it might be possible to extend the inline engine to support that as well.

Technically it's no problem at all. I did blitting of compressed sprites with transparency, alpha blending and color shift (aka fog and lighting) with DirectX7, so the engine certainly can take it (and a lot more. DirectX7 was a dream to work with for 2-d stuff!). The question is whether Martin wants to expend time for it...
 

jarmonik

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 28, 2008
Messages
2,666
Reaction score
795
Points
128
Technically it's no problem at all. I did blitting of compressed sprites with transparency, alpha blending and color shift (aka fog and lighting) with DirectX7, so the engine certainly can take it (and a lot more. DirectX7 was a dream to work with for 2-d stuff!). The question is whether Martin wants to expend time for it...

The sketchpad is an open source implementation and it's located in GDIClient.dll separately from a Orbiter itself so, the modification wouldn't require Martin's help. The only problem is that the sketchpad doesn't contain any version information in a base class and the base class can't be modified without breaking the compatibility with existing add-ons.

So, something as stupid as this might be required:

Code:
// Check sketchpad version and exit if too old
if (skp->GetTextWidth("SkpVersion")<0x200)  return false;

// Cast the pointer to extended version
SketchPadV2 *skp2 = (SketchPadV2 *) skp;
skp2->CopyRect();


---------- Post added at 18:58 ---------- Previous post was at 18:29 ----------

Sorry, Looks like I was wrong about that. The sketchpad is hardcoded in the Orbiter and can't be modified externally. The GDIClient.dll is only used with D3D7Client. Then why is it included in Orbiter distributuin if it isn't used.
 

jedidia

shoemaker without legs
Addon Developer
Joined
Mar 19, 2008
Messages
10,866
Reaction score
2,127
Points
203
Location
between the planets
I think it was included so other clients could use it if they want to, at least the documentation seams to suggest that.
 
Top