package com.android.driving.view; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Vector; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.graphics.PixelFormat; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import com.android.driving.Constant; /** * OpenGL Surface view */ public class VideoRendererView extends GLSurfaceView implements GLSurfaceView.Renderer { private final static String TAG = "VideoRendererViewTAG"+ Constant.EXT_TAG; private final int mMaxWidth = Constant.VIDEO_WIDTH_1080P; private final int mMaxHeight = Constant.VIDEO_HEIGHT_1080P; int mBufferWidthX, mBufferHeightY, mBufferWidthUV, mBufferHeightUV; ByteBuffer mBuffer; int mBufferPositionY, mBufferPositionU, mBufferPositionV; private static final int FLOAT_SIZE_BYTES = 4; private static final int SHORT_SIZE_BYTES = 2; private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; private final float[] TRIANFLE_VERTICES_DATA = { 1, -1, 0, 1, 1, 1, 1, 0, 1, 0, -1, 1, 0, 0, 0, -1, -1, 0, 0, 1 }; private final short[] INDICES_DATA = { 0, 1, 2, 2, 3, 0}; private FloatBuffer mTriangleVertices; private ShortBuffer mIndices; private static final String VERTEX_SHADER_SOURCE = "attribute vec4 aPosition;\n" + "attribute vec2 aTextureCoord;\n" + "varying vec2 vTextureCoord;\n" + "void main() {\n" + " gl_Position = aPosition;\n" + " vTextureCoord = aTextureCoord;\n" + "}\n"; private static final String FRAGMENT_SHADER_SOURCE = "precision mediump float;" + "varying vec2 vTextureCoord;" + "" + "uniform sampler2D SamplerY; " + "uniform sampler2D SamplerU;" + "uniform sampler2D SamplerV;" + "" + "const mat3 yuv2rgb = mat3(1, 0, 1.2802,1, -0.214821, -0.380589,1, 2.127982, 0);" + "" + "void main() { " + " vec3 yuv = vec3(1.1643 * (texture2D(SamplerY, vTextureCoord).r - 0.0625)," + " texture2D(SamplerV, vTextureCoord).r - 0.5," + " texture2D(SamplerU, vTextureCoord).r - 0.5);" + " vec3 rgb = yuv * yuv2rgb; " + " gl_FragColor = vec4(rgb, 1.0);" + "} "; private int mProgram = 0; private int maPositionHandle; private int maTextureHandle; private int muSamplerYHandle; private int muSamplerUHandle; private int muSamplerVHandle; private int[] mTextureY = new int[1]; private int[] mTextureU = new int[1]; private int[] mTextureV = new int[1]; private boolean mSurfaceCreated; private boolean mSurfaceDestroyed; @SuppressWarnings("unused") private Context mContext; private int mViewWidth, mViewHeight, mViewX, mViewY; private boolean mFullScreenRequired; private int mBufSize = 0 ; private int mCurChannel = Constant.CHANNEL_BACK; private Object mLock = new Object(); private boolean mIsFront = false; public VideoRendererView(Context context) { super(context, null); } public VideoRendererView(Context context, AttributeSet attrs) { super(context, attrs); setEGLContextClientVersion(2); setEGLConfigChooser(8, 8, 8, 8, 16, 0); setRenderer(this); getHolder().setFormat(PixelFormat.TRANSLUCENT); getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU); setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); mContext = context; mTriangleVertices = ByteBuffer.allocateDirect(TRIANFLE_VERTICES_DATA.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer(); mTriangleVertices.put(TRIANFLE_VERTICES_DATA).position(0); mIndices = ByteBuffer.allocateDirect(INDICES_DATA.length * SHORT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asShortBuffer(); mIndices.put(INDICES_DATA).position(0); mBufferWidthX = 0; mBufferHeightY = 0; if(Constant.CUT_YUV_SHOW){ mBufSize = mMaxWidth * mMaxHeight * 3 / 2; mBuffer = ByteBuffer.allocateDirect(mBufSize); }else { mBufSize = mMaxWidth * mMaxHeight * 3 / 2; mBuffer = ByteBuffer.allocateDirect(mBufSize); } } public void setParams(boolean isFront,boolean fullScreenRequired, ByteBuffer buffer, int bufferWidth, int bufferHeight) { mFullScreenRequired = fullScreenRequired; mIsFront = isFront; if(mIsFront){ mCurChannel = Constant.CHANNEL_FRONT; }else { mCurChannel = Constant.CHANNEL_BACK; } setBuffer(buffer, bufferWidth, bufferHeight); } /*public void notifyVideoSizeChange(boolean fullScreenRequired, ByteBuffer buffer, int newW, int newH) { Log.d(TAG,"notifyVideoSizeChange newW="+newW+"; newH="+newH); setParams(fullScreenRequired, buffer, newW, newH); //mBuffer = ByteBuffer.allocate(mBufferWidthX*mBufferHeightY*3/2); }*/ public void setBuffer(ByteBuffer buffer, int bufferWidth, int bufferHeight){ /*if((bufferWidth == mBufferWidthX) && (bufferHeight == mBufferHeightY)){ return; }*/ mBufferWidthX = bufferWidth; mBufferHeightY = bufferHeight; //int length = mBufferWidthX * mBufferHeightY * 3 / 2; //byte buf[] = new byte[length]; //mBuffer = ByteBuffer.wrap(buf); //mBuffer = buffer; mBufferWidthUV = (mBufferWidthX >> 1); mBufferHeightUV = (mBufferHeightY >> 1); mBufferPositionY = 0; mBufferPositionU = (mBufferWidthX * mBufferHeightY); mBufferPositionV = (mBufferPositionU + (mBufferWidthUV * mBufferHeightUV)); synchronized (mLock){ mValidDataList.clear(); mEmptyDataList.clear(); for(int i = 0; i < 3; i++){ byte data[] = new byte[mBufSize]; mEmptyDataList.add(data); } //默认输入一帧黑图 byte buf[] = mEmptyDataList.remove(0); for(int i = 0; i < mBufferHeightY; i++){ buf[i] = 16; } int vLength = mBufferWidthUV * mBufferHeightUV; byte blackU = (byte) 128; byte blackV = blackU; for(int i = 0; i < vLength; i++){ buf[mBufferPositionU + i] = blackU; } for(int i = 0; i < vLength; i++){ buf[mBufferPositionV + i] = blackV; } mValidDataList.add(buf); Log.d(TAG,"setBuffer bufferWidth = " + bufferWidth + " bufferHeight = " + bufferHeight + " mBufSize = " + mBufSize + " mEmptyDataList.size = " + mEmptyDataList.size() ); } } @Override public void onDrawFrame(GL10 glUnused) { if (mProgram == 0) { Log.i(TAG, "onDrawFrame mProgram = " + mProgram); return; } if(mIsFront){ Log.i(TAG, "onDrawFrame mIsFront =true; mProgram = " + mProgram); }else { Log.i(TAG, "onDrawFrame mIsFront =false; mProgram = " + mProgram); } GLES20.glViewport(mViewX, mViewY, mViewWidth, mViewHeight); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); GLES20.glUseProgram(mProgram); checkGlError("glUseProgram"); byte[] newData = null; synchronized (mLock){ if (mValidDataList.isEmpty()) { return; } /*if (mBuffer == null) { mBuffer = ByteBuffer.allocateDirect(mBufferWidthX*mBufferHeightY*3/2); }else{ if(mBuffer.capacity() != mBufferWidthX*mBufferHeightY*3/2){ //正常情况下,不会执行到此 mValidDataList.clear(); mEmptyDataList.clear(); Log.d(TAG,"onDrawFrame ERROR mValidDataList.clear(); mEmptyDataList.clear()"); return; //mBuffer = ByteBuffer.allocateDirect(mBufferWidthX*mBufferHeightY*3/2); } }*/ newData = mValidDataList.remove(0);//mValidDataList.get(0); } /*Log.d(TAG,"onDrawFrame mBuffer.capacity()="+mBuffer.capacity()+"; =="+mBufferWidthX*mBufferHeightY*3/2+";W="+mBufferWidthX+";H="+mBufferHeightY+";newData.size=" +newData.length);*/ /*if(mBuffer.capacity() != newData.length){ //正常情况下,不会执行到此 Log.e(TAG,"onDrawFrame ERROR mBuffer.capacity()="+mBuffer.capacity()+"; newData.length=="+newData.length); throw new RuntimeException(); }*/ //mBuffer.rewind(); mBuffer.clear(); mBuffer.put(newData); if(mBuffer != null){ synchronized(this){ GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, mTextureY[0]); GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mBufferWidthX, mBufferHeightY, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, mBuffer.position(mBufferPositionY)); GLES20.glUniform1i(muSamplerYHandle, 0); GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, mTextureU[0]); GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mBufferWidthUV, mBufferHeightUV, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, mBuffer.position(mBufferPositionU)); GLES20.glUniform1i(muSamplerUHandle, 1); GLES20.glActiveTexture(GLES20.GL_TEXTURE2); GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, mTextureV[0]); GLES20.glTexImage2D( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, mBufferWidthUV, mBufferHeightUV, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, mBuffer.position(mBufferPositionV)); GLES20.glUniform1i(muSamplerVHandle, 2); } } GLES20.glDrawElements(GLES20.GL_TRIANGLES, INDICES_DATA.length, GLES20.GL_UNSIGNED_SHORT, mIndices); synchronized (mLock) { mEmptyDataList.add(newData); } } public void onSurfaceChanged(GL10 glUnused, int width, int height) { GLES20.glViewport(0, 0, width, height); setViewport(width, height); // GLU.gluPerspective(glUnused, 45.0f, (float)width/(float)height, 0.1f, 100.0f); } public boolean isReady(){ return (mSurfaceCreated && !mSurfaceDestroyed); } public boolean isDestroyed(){ return mSurfaceDestroyed; } @Override public void surfaceDestroyed(SurfaceHolder holder) { mSurfaceCreated = false; mSurfaceDestroyed = true; super.surfaceDestroyed(holder); Log.i(TAG, "surfaceDestroyed mProgram = " + mProgram); } public void clearBuf(){ synchronized (mLock) { mEmptyDataList.clear(); mValidDataList.clear(); Log.i(TAG, "clearBuf myTid = " + android.os.Process.myTid() + " mValidDataList.size = " + mValidDataList.size() + " mEmptyDataList.size = " + mEmptyDataList.size()); } } public void release(){ Log.i(TAG, "release myTid = " + android.os.Process.myTid() + " mProgram = " + mProgram + " mPixelShader = " + mPixelShader + " mVertexShader = " + mVertexShader); if(mProgram != 0){ GLES20.glDeleteProgram(mProgram); GLES20.glDeleteTextures(1, mTextureY, 0); GLES20.glDeleteTextures(1, mTextureU, 0); GLES20.glDeleteTextures(1, mTextureV, 0); mProgram = 0; } if(mPixelShader != 0){ GLES20.glDeleteShader(mPixelShader); mPixelShader = 0; } if(mVertexShader != 0){ GLES20.glDeleteShader(mVertexShader); mVertexShader = 0; } } public void onSurfaceCreated(GL10 glUnused, EGLConfig config) { createTexture(); mSurfaceCreated = true; setViewport(getWidth(), getHeight()); } private void createTexture(){ if(mProgram != 0){ Log.d(TAG, "createTexture already create"); return; } GLES20.glEnable( GLES20.GL_BLEND); GLES20.glDisable(GLES20.GL_DEPTH_TEST); GLES20.glDisable(GLES20.GL_DITHER); GLES20.glDisable(GLES20.GL_STENCIL_TEST); GLES20.glDisable(GL10.GL_DITHER); String extensions = GLES20.glGetString(GL10.GL_EXTENSIONS); // Ignore the passed-in GL10 interface, and use the GLES20 // class's static methods instead. mProgram = createProgram(VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE); if (mProgram == 0) { Log.d(TAG, "createTexture Program fail"); return; } Log.d(TAG, "createTexture + myTid = " +android.os.Process.myTid() + " mProgram = " +mProgram); maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); checkGlError("glGetAttribLocation aPosition"); if (maPositionHandle == -1) { throw new RuntimeException("Could not get attrib location for aPosition"); } maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); checkGlError("glGetAttribLocation aTextureCoord"); if (maTextureHandle == -1) { throw new RuntimeException("Could not get attrib location for aTextureCoord"); } muSamplerYHandle = GLES20.glGetUniformLocation(mProgram, "SamplerY"); if (muSamplerYHandle == -1) { throw new RuntimeException("Could not get uniform location for SamplerY"); } muSamplerUHandle = GLES20.glGetUniformLocation(mProgram, "SamplerU"); if (muSamplerUHandle == -1) { throw new RuntimeException("Could not get uniform location for SamplerU"); } muSamplerVHandle = GLES20.glGetUniformLocation(mProgram, "SamplerV"); if (muSamplerVHandle == -1) { throw new RuntimeException("Could not get uniform location for SamplerV"); } mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); checkGlError("glVertexAttribPointer maPosition"); mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); GLES20.glEnableVertexAttribArray(maPositionHandle); checkGlError("glEnableVertexAttribArray maPositionHandle"); GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); checkGlError("glVertexAttribPointer maTextureHandle"); GLES20.glEnableVertexAttribArray(maTextureHandle); checkGlError("glEnableVertexAttribArray maTextureHandle"); GLES20.glGenTextures(1, mTextureY, 0); GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, mTextureY[0]); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glGenTextures(1, mTextureU, 0); GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, mTextureU[0]); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glGenTextures(1, mTextureV, 0); GLES20.glBindTexture( GLES20.GL_TEXTURE_2D, mTextureV[0]); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); } private int mVertexShader = 0; private int mPixelShader = 0; private int createProgram(String vertexSource, String fragmentSource) { int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); if (vertexShader == 0) { Log.d(TAG, "createProgram vertexShader fail"); return 0; } mVertexShader= vertexShader; int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); if (pixelShader == 0) { Log.d(TAG, "createProgram pixelShader fail"); return 0; } mPixelShader = pixelShader; int program = GLES20.glCreateProgram(); if (program != 0) { GLES20.glAttachShader(program, vertexShader); checkGlError("glAttachShader"); GLES20.glAttachShader(program, pixelShader); checkGlError("glAttachShader"); GLES20.glLinkProgram(program); int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { Log.e(TAG, "Could not link program: "); Log.e(TAG, GLES20.glGetProgramInfoLog(program)); GLES20.glDeleteProgram(program); program = 0; } } return program; } private int loadShader(int shaderType, String source) { int shader = GLES20.glCreateShader(shaderType); if (shader != 0) { GLES20.glShaderSource(shader, source); GLES20.glCompileShader(shader); int[] compiled = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); if (compiled[0] == 0) { Log.e(TAG, "Could not compile shader " + shaderType + ":"); Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); shader = 0; } } return shader; } private void setViewport(int width, int height){ if(mFullScreenRequired){ mViewWidth = width; mViewHeight = height; mViewX = mViewY = 0; if(mIsFront){ mViewX = mViewY = 0; }else{ mViewY = 0; mViewX = 0; } } else{ float fRatio = ((float) mBufferWidthX / (float) mBufferHeightY); mViewWidth = (int) ((float) width / fRatio) > height ? (int) ((float) height * fRatio) : width; mViewHeight = (int) (mViewWidth / fRatio) > height ? height : (int) (mViewWidth / fRatio); mViewX = ((width - mViewWidth) >> 1); mViewY = ((height - mViewHeight) >> 1); if(mIsFront){ mViewX = mViewY = 0; }else{ mViewY = 0; mViewX = 0; //this.getPivotX() } } Log.i(TAG, "setViewport mViewWidth = " + mViewWidth + " mViewHeight = " + mViewHeight+"; width="+width+"; height="+height+"; mIsFront= "+mIsFront +"; mViewX="+mViewX); } private void checkGlError(String op) { int error; while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { Log.e(TAG, op + ": glError " + error); throw new RuntimeException(op + ": glError " + error); } } private final static int BUFFER_COUNT_MIN = 1; private Vector<byte[]> mValidDataList = new Vector<byte[]>(); private Vector<byte[]> mEmptyDataList = new Vector<byte[]>(); public void newDataArrived(final byte[] data, int channel) { /*if(channel != mCurChannel){ return; }*/ byte[] newData; synchronized (mLock){ if (mEmptyDataList.size() > 0) { newData = mEmptyDataList.remove(0); } else { //Log.d(TAG,"newDataArrived data.size="+data.length+";mEmptyDataList.size=="+mEmptyDataList.size()); //if(mValidDataList.size() >= 2){ return; //} //newData = new byte[data.length]; } } Log.d(TAG,"newDataArrived data.size="+data.length+";newData.size="+newData.length+"; mValidDataList.size="+mValidDataList.size() + " mEmptyDataList.size = " + mEmptyDataList.size() +";mCurChannel="+mCurChannel+"; channel="+channel); if(Constant.CUT_YUV_SHOW){ if(mIsFront) { Cut_YV12(data, 0, 0, mBufferWidthX*2, mBufferHeightY, newData, mBufferWidthX, mBufferHeightY); }else{ //显示后半部分 Cut_YV12(data, mBufferWidthX , 0, mBufferWidthX*2, mBufferHeightY, newData, mBufferWidthX, mBufferHeightY); } }else { System.arraycopy(data, 0, newData, 0, data.length); } mValidDataList.add(newData); this.requestRender(); } public void switchView(){ if(mCurChannel == Constant.CHANNEL_FRONT){ mCurChannel = Constant.CHANNEL_BACK; }else { mCurChannel = Constant.CHANNEL_FRONT; } } static void Cut_YV12(byte[] Src,int x,int y,int srcWidth,int srcHeight,byte[] Dst,int desWidth,int desHeight)//图片按位置裁剪 { //得到B图像所在A的坐标 int nIndex=0; int BPosX=x ;//列 int BPosY=y;//行 for(int i=0;i<desHeight;i++)// { //memcpy(Dst+desWidth*i,Src+(srcWidth*BPosY)+BPosX+nIndex,desWidth); //arraycopy(Object src, int srcPos, Object dest, int destPos, int length) System.arraycopy(Src, (srcWidth*BPosY)+BPosX+nIndex, Dst, desWidth*i, desWidth); nIndex+=(srcWidth); } nIndex=0; //BYTE *pUSour=Src+srcWidth*srcHeight; //BYTE *pUDest=Dst+desWidth*desHeight; int posUSour = srcWidth*srcHeight; int posUDest = desWidth*desHeight; for(int i=0;i<desHeight/2;i++)// { //memcpy(pUDest+desWidth/2*i,pUSour+(srcWidth/2*BPosY/2)+BPosX/2+nIndex,desWidth/2); System.arraycopy(Src, posUSour+ (srcWidth/2*BPosY/2)+BPosX/2+nIndex, Dst, posUDest+ desWidth/2*i, desWidth/2); nIndex+=(srcWidth/2); } nIndex=0; //BYTE *pVSour=Src+srcWidth*srcHeight*5/4; //BYTE *pVDest=Dst+desWidth*desHeight*5/4; posUSour = srcWidth*srcHeight*5/4; posUDest = desWidth*desHeight*5/4; for(int i=0;i<desHeight/2;i++)// { //memcpy(pVDest+desWidth/2*i,pVSour+(srcWidth/2*BPosY/2)+BPosX/2+nIndex,desWidth/2); System.arraycopy(Src, posUSour+ (srcWidth/2*BPosY/2)+BPosX/2+nIndex, Dst, posUDest+ desWidth/2*i, desWidth/2); nIndex+=(srcWidth/2); } } }
OpenGL Surface view
最新推荐文章于 2024-11-03 15:07:34 发布