Lots of changes

- Hacky fix for weird "out-of-body" issue when not drawing hud
- HUD is now drawn as a world sprite (this has its own issues, but is mostly there)
- Package the pk3 as part of the build
This commit is contained in:
Simon 2022-04-02 23:34:11 +01:00
parent 3639fc6d6a
commit 5a6a7cfd66
23 changed files with 336 additions and 80 deletions

View file

@ -2761,6 +2761,34 @@ static void CG_Draw2D()
}
//
// HACK HACK HACK
//
//Render an empty scene - seems to sort the weird out-of-body thing
//when the HUD isn't being drawn. Need to get to the bottom of this
//it shouldn't cost frames, but it is ugly
static void CG_EmptySceneHackHackHack( void )
{
refdef_t refdef;
memset( &refdef, 0, sizeof( refdef ) );
refdef.rdflags = RDF_NOWORLDMODEL;
AxisClear( refdef.viewaxis );
refdef.fov_x = 30;
refdef.fov_y = 30;
refdef.x = 0;
refdef.y = 0;
refdef.width = cgs.glconfig.vidWidth;
refdef.height = cgs.glconfig.vidHeight;
refdef.time = cg.time;
trap_R_ClearScene();
trap_R_RenderScene( &refdef );
}
/*
=====================
CG_DrawActive
@ -2852,13 +2880,50 @@ void CG_DrawActive( void ) {
}
}
//Now draw the HUD shader in the world
{
refEntity_t ent;
trace_t trace;
vec3_t viewaxis[3];
vec3_t weaponangles;
vec3_t origin, endpos;
float scale = trap_Cvar_VariableValue("vr_worldscaleScaler");
float dist = (trap_Cvar_VariableValue("vr_hudDepth")+1) * 6 * scale;
float radius = dist / 3.0f;
VectorMA(cg.refdef.vieworg, dist, cg.refdef.viewaxis[0], endpos);
VectorMA(endpos, trap_Cvar_VariableValue("vr_hudYOffset") / 20, cg.refdef.viewaxis[2], endpos);
memset(&ent, 0, sizeof(ent));
ent.reType = RT_SPRITE;
ent.renderfx = RF_DEPTHHACK;
VectorCopy(endpos, ent.origin);
ent.radius = radius;
ent.invert = qtrue;
ent.customShader = cgs.media.hudShader;
trap_R_AddRefEntityToScene(&ent);
}
// draw 3D view
trap_R_RenderScene( &cg.refdef );
VectorCopy( baseOrg, cg.refdef.vieworg );
{
//Tell renderer we want to draw to the HUD buffer
trap_R_HUDBufferStart();
// draw status bar and other floating elements
CG_Draw2D();
trap_R_HUDBufferEnd();
}
CG_EmptySceneHackHackHack();
}

View file

@ -44,17 +44,28 @@ CG_AdjustFrom640
Adjusted for resolution and screen aspect ratio
================
*/
void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) {
if (vr->virtual_screen ||
vr->weapon_zoomed)
void CG_AdjustFrom640( float *x, float *y, float *w, float *h )
{
/* if ( cg.snap == NULL ||
cg.snap->ps.pm_type != PM_INTERMISSION)*/
{
// scale for screen sizes
*x *= cgs.screenXScale;
*y *= cgs.screenYScale;
if (hudflags & HUD_FLAGS_DRAWMODEL)
{
*w *= (cgs.screenXScale * 2.0f);
*x -= (*w / 3);
*h *= (cgs.screenYScale * 2.0f);
*y -= (*h / 3);
}
else
{
*w *= cgs.screenXScale;
*h *= cgs.screenYScale;
}
}
/*
else // scale to clearly visible portion of VR screen
{
float screenXScale = cgs.screenXScale / 2.75f;
@ -76,8 +87,10 @@ void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) {
}
*x += (cg.refdef.width - (640 * screenXScale)) / 2.0f;
*y += (cg.refdef.height - (480 * screenYScale)) / 2.0f - trap_Cvar_VariableValue( "vr_hudYOffset" );
*y += (cg.refdef.height - (480 * screenYScale)) / 2.0f -
trap_Cvar_VariableValue("vr_hudYOffset");
}
*/
}
/*

View file

@ -833,6 +833,8 @@ typedef struct {
qhandle_t invulnerabilityPowerupModel;
#endif
qhandle_t hudShader;
// scoreboard headers
qhandle_t scoreboardName;
qhandle_t scoreboardPing;
@ -1659,6 +1661,9 @@ int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int
void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset );
qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 );
void trap_R_HUDBufferStart( void );
void trap_R_HUDBufferEnd( void );
// The glconfig_t will not change during the life of a cgame.
// If it needs to change, the entire cgame will be restarted, because
// all the qhandle_t are then invalid.

View file

@ -1053,6 +1053,9 @@ static void CG_RegisterGraphics( void ) {
cgs.media.reticleShader = trap_R_RegisterShader( "gfx/weapon/scope" );
cgs.media.vignetteShader = trap_R_RegisterShader( "gfx/vignette" );
//HUD
cgs.media.hudShader = trap_R_RegisterShader( "sprites/vr/hud" );
//Used for the weapon selector
cgs.media.smallSphereModel = trap_R_RegisterModel("models/powerups/health/small_sphere.md3");

View file

@ -166,6 +166,8 @@ typedef enum {
CG_FS_SEEK,
CG_HAPTICEVENT,
CG_R_HUDBUFFER_START,
CG_R_HUDBUFFER_END,
/*
CG_LOADCAMERA,

View file

@ -288,6 +288,14 @@ void trap_R_RenderScene( const refdef_t *fd ) {
syscall( CG_R_RENDERSCENE, fd );
}
void trap_R_HUDBufferStart( void ) {
syscall( CG_R_HUDBUFFER_START );
}
void trap_R_HUDBufferEnd( void ) {
syscall( CG_R_HUDBUFFER_END );
}
void trap_R_SetColor( const float *rgba ) {
syscall( CG_R_SETCOLOR, rgba );
}

View file

@ -697,6 +697,12 @@ intptr_t CL_CgameSystemCalls( intptr_t *args ) {
case CG_HAPTICEVENT:
VR_HapticEvent( VMA(1), args[2], args[3], args[4], VMF(5), VMF(6) );
return 0;
case CG_R_HUDBUFFER_START:
re.HUDBufferStart();
return 0;
case CG_R_HUDBUFFER_END:
re.HUDBufferEnd();
return 0;
default:

View file

@ -75,6 +75,8 @@ typedef struct {
void (*AddLightToScene)( const vec3_t org, float intensity, float r, float g, float b );
void (*AddAdditiveLightToScene)( const vec3_t org, float intensity, float r, float g, float b );
void (*RenderScene)( const refdef_t *fd );
void (*HUDBufferStart)( void );
void (*HUDBufferEnd)( void );
void (*SetColor)( const float *rgba ); // NULL = 1,1,1,1
void (*DrawStretchPic) ( float x, float y, float w, float h,

View file

@ -118,6 +118,7 @@ typedef struct {
// extra sprite information
float radius;
float rotation;
qboolean invert;
} refEntity_t;

View file

@ -530,6 +530,7 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
}
GL_SetModelMatrix( backEnd.or.modelMatrix );
GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
//
// change depthrange. Also change projection matrix so first person weapon does not look like coming
@ -539,23 +540,6 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
{
if (depthRange)
{
if(isCrosshair)
{
if(oldDepthRange)
{
// was not a crosshair but now is, change back proj matrix
GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
}
}
else
{
viewParms_t temp = backEnd.viewParms;
R_SetupProjection(&temp, r_znear->value, 0, qfalse);
GL_SetProjectionMatrix( temp.projectionMatrix );
}
#ifdef __ANDROID__
if(!oldDepthRange)
glDepthRangef(0.0f, 0.3f);
@ -566,11 +550,6 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
}
else
{
if(!wasCrosshair)
{
GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
}
#ifdef __ANDROID__
glDepthRangef(0.0f, 1.0f);
#else
@ -633,6 +612,7 @@ void RB_SetGL2D (void) {
return;
backEnd.projection2D = qtrue;
backEnd.last2DFBO = glState.currentFBO;
if (glState.currentFBO)
@ -1776,6 +1756,55 @@ const void* RB_SwitchEye( const void* data ) {
return (const void*)(cmd + 1);
}
/*
====================
RB_HUDBuffer
====================
*/
const void* RB_HUDBuffer( const void* data ) {
const hudBufferCommand_t *cmd = data;
// finish any 2D drawing if needed
if(tess.numIndexes)
RB_EndSurface();
if (cmd->start && tr.renderFbo->frameBuffer != tr.hudFbo->frameBuffer)
{
//keep record of current render fbo and switch to the hud buffer
tr.backupFrameBuffer = tr.renderFbo->frameBuffer;
tr.renderFbo->frameBuffer = tr.hudFbo->frameBuffer;
// Render to framebuffer
GL_BindFramebuffer(GL_FRAMEBUFFER, tr.hudFbo->frameBuffer);
qglBindRenderbuffer(GL_RENDERBUFFER, 0);
qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tr.hudImage->texnum, 0);
// Attach combined depth+stencil
qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, tr.hudDepthImage->texnum);
qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, tr.hudDepthImage->texnum);
GLenum result = qglCheckFramebufferStatus(GL_FRAMEBUFFER);
if(result != GL_FRAMEBUFFER_COMPLETE)
{
ri.Error( "Error binding Framebuffer: %i\n", result );
}
qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
qglClear( GL_COLOR_BUFFER_BIT );
}
else if (tr.renderFbo->frameBuffer == tr.hudFbo->frameBuffer)
{
//restore the true render fbo
tr.renderFbo->frameBuffer = tr.backupFrameBuffer;
GL_BindFramebuffer(GL_FRAMEBUFFER, tr.renderFbo->frameBuffer);
}
glState.currentFBO = tr.renderFbo;
return (const void*)(cmd + 1);
}
/*
====================
RB_ExecuteRenderCommands
@ -1829,6 +1858,9 @@ void RB_ExecuteRenderCommands( const void *data ) {
case RC_SWITCH_EYE:
data = RB_SwitchEye(data);
break;
case RC_HUD_BUFFER:
data = RB_HUDBuffer(data);
break;
case RC_END_OF_LIST:
default:
// finish any 2D drawing if needed

View file

@ -430,8 +430,6 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) {
}
tr.refdef.stereoFrame = stereoFrame;
GLSL_PrepareUniformBuffers();
}
@ -474,6 +472,36 @@ void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) {
backEnd.pc.msec = 0;
}
void RE_HUDBufferStart( void )
{
hudBufferCommand_t *cmd;
if ( !tr.registered ) {
return;
}
cmd = R_GetCommandBufferReserved( sizeof( *cmd ), 0 );
if ( !cmd ) {
return;
}
cmd->start = qtrue;
cmd->commandId = RC_HUD_BUFFER;
}
void RE_HUDBufferEnd( void )
{
hudBufferCommand_t *cmd;
if ( !tr.registered ) {
return;
}
cmd = R_GetCommandBufferReserved( sizeof( *cmd ), 0 );
if ( !cmd ) {
return;
}
cmd->start = qfalse;
cmd->commandId = RC_HUD_BUFFER;
}
//#if __ANDROID__
void R_Mat4Transpose( const float in[4][4], float* out ) {
int i, j;

View file

@ -306,6 +306,21 @@ void FBO_Init(void)
qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
{
tr.hudFbo = FBO_Create("_hud", tr.hudImage->width, tr.hudImage->height);
FBO_AttachImage(tr.hudFbo, tr.hudImage, GL_COLOR_ATTACHMENT0, 0);
FBO_AttachImage(tr.hudFbo, tr.hudDepthImage, GL_DEPTH_ATTACHMENT, 0);
R_CheckFBO(tr.hudFbo);
// clear render buffer
if (tr.hudFbo)
{
GL_BindFramebuffer(GL_FRAMEBUFFER, tr.hudFbo->frameBuffer);
qglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
}
if (tr.screenScratchImage)
{
tr.screenScratchFbo = FBO_Create("screenScratch", tr.screenScratchImage->width, tr.screenScratchImage->height);

View file

@ -66,11 +66,18 @@ typedef struct uniformInfo_s
}
uniformInfo_t;
#define ORTHO_PROJECTION 0
#define NORMAL_PROJECTION 1
typedef enum {
// STEREO_ORTHO_PROJECTION, // An orthographic projection with slight stereo view matrix
HUD_ORTHO_PROJECTION, // Orthographic projection and no stereo view
VR_PROJECTION,
GLuint viewMatricesBuffer[2];
GLuint projectionMatricesBuffer[2];
PROJECTION_COUNT
} projection_t;
GLuint viewMatricesBuffer[PROJECTION_COUNT];
GLuint projectionMatricesBuffer[PROJECTION_COUNT];
float orthoProjectionMatrix[16];
// These must be in the same order as in uniform_t in tr_local.h.
@ -180,7 +187,7 @@ GLSL_ViewMatricesUniformBuffer
*/
static void GLSL_ViewMatricesUniformBuffer(const float value[32]) {
for (int i = 0; i < 2; ++i)
for (int i = 0; i < PROJECTION_COUNT; ++i)
{
// Update the scene matrices for when we are using a normal projection
qglBindBuffer(GL_UNIFORM_BUFFER, viewMatricesBuffer[i]);
@ -196,23 +203,31 @@ static void GLSL_ViewMatricesUniformBuffer(const float value[32]) {
return;
}
if (i == ORTHO_PROJECTION)
switch (i)
{
case HUD_ORTHO_PROJECTION:
{
if (vr.virtual_screen)
{
//don't want depth when in screen view
const auto depth = (VR_useScreenLayer() || vr.weapon_zoomed) ? 0 : (5-vr_hudDepth->integer) * 20;
//For now just set identity matrices
vec3_t translate;
VectorSet(translate, depth, 0, 0);
VectorSet(translate, -10, 0, 0);
Mat4Translation( translate, viewMatrices );
VectorSet(translate, -depth, 0, 0);
VectorSet(translate, 10, 0, 0);
Mat4Translation( translate, viewMatrices + 16 );
}
else
{
Mat4Identity( viewMatrices );
Mat4Identity( viewMatrices + 16 );
}
}
break;
default:
{
memcpy((char *) viewMatrices, value, 32 * sizeof(float));
}
break;
}
qglUnmapBuffer(GL_UNIFORM_BUFFER);
qglBindBuffer(GL_UNIFORM_BUFFER, 0);
@ -1056,7 +1071,7 @@ void GLSL_InitGPUShaders(void)
ri.Printf(PRINT_ALL, "------- GLSL_InitGPUShaders -------\n");
for (int i = 0; i < 2; ++i)
for (int i = 0; i < PROJECTION_COUNT; ++i)
{
//Generate buffer for 2 * view matrices
qglGenBuffers(1, &viewMatricesBuffer[i]);
@ -1660,15 +1675,17 @@ void GLSL_PrepareUniformBuffers(void)
height = glConfig.vidHeight;
}
float orthoProjectionMatrix[16];
Mat4Ortho(0, width, height, 0, 0, 1, orthoProjectionMatrix);
//ortho projection matrix
GLSL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[ORTHO_PROJECTION],
/* GLSL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[STEREO_ORTHO_PROJECTION],
orthoProjectionMatrix);
*/
GLSL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[HUD_ORTHO_PROJECTION],
orthoProjectionMatrix);
//VR projection matrix
GLSL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[NORMAL_PROJECTION],
GLSL_ProjectionMatricesUniformBuffer(projectionMatricesBuffer[VR_PROJECTION],
tr.vrParms.projection);
//Set up the buffers that won't change this frame
@ -1691,10 +1708,11 @@ void GLSL_BindProgram(shaderProgram_t * program)
}
static GLuint GLSL_CalculateProjection() {
GLuint result = NORMAL_PROJECTION;
if (backEnd.projection2D)
GLuint result = VR_PROJECTION;
if (Mat4Compare(&orthoProjectionMatrix, glState.projection))
{
result = ORTHO_PROJECTION;
result = HUD_ORTHO_PROJECTION;
}
return result;

View file

@ -2784,6 +2784,9 @@ void R_CreateBuiltinImages( void ) {
tr.renderImage = R_CreateImage("_render", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat);
tr.hudImage = R_CreateImage("hudImage", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat);
tr.hudDepthImage = R_CreateImage("*hudDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24);
if (r_shadowBlur->integer)
tr.screenScratchImage = R_CreateImage("screenScratch", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat);

View file

@ -1652,6 +1652,8 @@ refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) {
re.AddLightToScene = RE_AddLightToScene;
re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene;
re.RenderScene = RE_RenderScene;
re.HUDBufferStart = RE_HUDBufferStart;
re.HUDBufferEnd = RE_HUDBufferEnd;
re.SetColor = RE_SetColor;
re.DrawStretchPic = RE_StretchPic;

View file

@ -1530,6 +1530,7 @@ typedef struct {
image_t *renderImage;
image_t *sunRaysImage;
image_t *renderDepthImage;
image_t *hudDepthImage;
image_t *pshadowMaps[MAX_DRAWN_PSHADOWS];
image_t *screenScratchImage;
image_t *textureScratchImage[2];
@ -1542,6 +1543,7 @@ typedef struct {
image_t *screenSsaoImage;
image_t *hdrDepthImage;
image_t *renderCubeImage;
image_t *hudImage;
image_t *textureDepthImage;
@ -1560,6 +1562,7 @@ typedef struct {
FBO_t *screenSsaoFbo;
FBO_t *hdrDepthFbo;
FBO_t *renderCubeFbo;
FBO_t *hudFbo;
shader_t *defaultShader;
shader_t *shadowShader;
@ -1569,6 +1572,8 @@ typedef struct {
shader_t *sunShader;
shader_t *sunFlareShader;
shader_t *hudShader;
int numLightmaps;
int lightmapSize;
image_t **lightmaps;
@ -1586,6 +1591,8 @@ typedef struct {
int shiftedEntityNum; // currentEntityNum << QSORT_REFENTITYNUM_SHIFT
model_t *currentModel;
int backupFrameBuffer;
//
// GPU shader programs
//
@ -2455,6 +2462,11 @@ typedef struct {
stereoFrame_t stereoFrame;
} switchEyeCommand_t;
typedef struct {
int commandId;
qboolean start;
} hudBufferCommand_t;
typedef enum {
RC_END_OF_LIST,
RC_SET_COLOR,
@ -2469,7 +2481,8 @@ typedef enum {
RC_CAPSHADOWMAP,
RC_POSTPROCESS,
RC_EXPORT_CUBEMAPS,
RC_SWITCH_EYE
RC_SWITCH_EYE,
RC_HUD_BUFFER
} renderCommand_t;
@ -2515,6 +2528,9 @@ void RE_EndFrame( int *frontEndMsec, int *backEndMsec );
void RE_SetVRHeadsetParms( const ovrMatrix4f *projectionMatrix,
int renderBuffer );
#endif
void RE_HUDBufferStart( void );
void RE_HUDBufferEnd( void );
void RE_SaveJPG(char * filename, int quality, int image_width, int image_height,
unsigned char *image_buffer, int padding);
size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality,

View file

@ -627,6 +627,14 @@ void R_RotateForViewer (void)
}
tr.viewParms.world = tr.or;
#if 0
Com_Printf("Origin = %g, %g, %g", tr.or.modelView[12], tr.or.modelView[13], tr.or.modelView[14]);
//Com_Printf("Origin = %g, %g, %g", tr.viewParms.or.origin[0], tr.viewParms.or.origin[1], tr.viewParms.or.origin[2]);
Com_Printf("Axis 0 = %g, %g, %g", tr.viewParms.or.axis[0][0], tr.viewParms.or.axis[0][1], tr.viewParms.or.axis[0][2]);
Com_Printf("Axis 1 = %g, %g, %g", tr.viewParms.or.axis[1][0], tr.viewParms.or.axis[1][1], tr.viewParms.or.axis[1][2]);
Com_Printf("Axis 2 = %g, %g, %g", tr.viewParms.or.axis[2][0], tr.viewParms.or.axis[2][1], tr.viewParms.or.axis[2][2]);
#endif
}
/*

View file

@ -3880,6 +3880,11 @@ static void CreateExternalShaders( void ) {
tr.sunFlareShader = FinishShader();
}
//Set some stuff on our HUD shader
{
tr.hudShader = R_FindShader("sprites/vr/hud", LIGHTMAP_2D, qfalse);
tr.hudShader->stages[0]->bundle[0].image[0] = tr.hudImage;
}
}
/*

View file

@ -250,7 +250,7 @@ static void RB_SurfaceSprite( void ) {
radius = ent->e.radius;
if ( ent->e.rotation == 0 ) {
VectorScale( backEnd.viewParms.or.axis[1], radius, left );
VectorScale( backEnd.viewParms.or.axis[2], radius, up );
VectorScale( backEnd.viewParms.or.axis[2], ent->e.invert ? -radius : radius, up );
} else {
float s, c;
float ang;

View file

@ -0,0 +1,6 @@
cd assets
del pakQ3Q.pk3
cd ..
powershell Compress-Archive pakQ3Q/* pakQ3Q.zip
rename pakQ3Q.zip pakQ3Q.pk3
move pakQ3Q.pk3 assets/

View file

@ -0,0 +1,13 @@
//
// VR Hud Shader Sprite - the image is just a placeholder and is replaced in code
//
sprites/vr/hud
{
cull disable
{
clampmap sprites/plasmaa.tga
blendfunc GL_ONE GL_ONE
}
}

View file

@ -3,7 +3,7 @@
setlocal
set BUILD_TYPE=release
set VERSION=0.29.0-multiview
set VERSION=0.29.1-multiview
@REM Define the following environment variables to sign a release build
@REM set KEYSTORE=
@ -29,6 +29,11 @@ if %BUILD_TYPE%==debug (
set GRADLE_BUILD_TYPE=:app:assembleDebug
)
REM package our special pk3
pushd android\app\src\main
call make_pakQ3Q.bat
popd
echo #define Q3QVERSION "%VERSION%" > .\android\app\src\main\cpp\code\vr\vr_version.h
pushd %~dp0\..